diff --git a/adapter/experimental.go b/adapter/experimental.go index 493d8027..d5a1e5bb 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -8,18 +8,9 @@ import ( "time" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/common/varbin" ) -type ClashServer interface { - LifecycleService - Mode() string - ModeList() []string - SetMode(mode string) - AddModeUpdateHook(hook *observable.Subscriber[struct{}]) -} - type URLTestHistory struct { Time time.Time `json:"time"` Delay uint16 `json:"delay"` diff --git a/box.go b/box.go index f06a1176..7bbedd59 100644 --- a/box.go +++ b/box.go @@ -27,6 +27,7 @@ import ( "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/cachefile" + "github.com/sagernet/sing-box/experimental/clashmode" "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -157,7 +158,7 @@ func New(options Options) (*Box, error) { if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil { needCacheFile = true } - if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil { + if experimentalOptions.ClashAPI != nil { needClashAPI = true } if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { @@ -177,7 +178,7 @@ func New(options Options) (*Box, error) { logFactory, err := log.New(log.Options{ Context: ctx, Options: common.PtrValueOrDefault(options.Log), - Observable: needClashAPI || needAPIService, + Observable: needClashAPI && experimentalOptions.ClashAPI.ExternalController != "", DefaultWriter: defaultLogWriter, BaseTime: createdAt, PlatformWriter: options.PlatformLogWriter, @@ -243,11 +244,18 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "initialize router") } - if needClashAPI || needAPIService { + if needClashAPI || needAPIService || options.PlatformLogWriter != nil { trafficManager := trafficcontrol.NewManager(outboundManager) service.MustRegisterPtr(ctx, trafficManager) router.AppendTracker(trafficManager) internalServices = append(internalServices, trafficManager) + var clashDefaultMode string + if experimentalOptions.ClashAPI != nil { + clashDefaultMode = experimentalOptions.ClashAPI.DefaultMode + } + clashMode := clashmode.NewManager(ctx, logFactory.NewLogger("clash-mode"), clashDefaultMode, clashmode.CalculateModeList(options.Options)) + service.MustRegisterPtr(ctx, clashMode) + internalServices = append(internalServices, clashMode) } ntpOptions := common.PtrValueOrDefault(options.NTP) var timeService *tls.TimeServiceWrapper @@ -420,13 +428,10 @@ func New(options Options) (*Box, error) { internalServices = append(internalServices, cacheFile) } if needClashAPI { - clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) - clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) - clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions) + clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(experimentalOptions.ClashAPI)) if err != nil { return nil, E.Cause(err, "create clash-server") } - service.MustRegister[adapter.ClashServer](ctx, clashServer) internalServices = append(internalServices, clashServer) } if needV2RayAPI { diff --git a/daemon/attached_service.go b/daemon/attached_service.go index b726ac2c..43c6b10a 100644 --- a/daemon/attached_service.go +++ b/daemon/attached_service.go @@ -3,8 +3,6 @@ package daemon import ( "context" "time" - - "github.com/sagernet/sing-box/log" ) const defaultAttachedLogMaxLines = 3000 @@ -20,9 +18,9 @@ func NewAttachedService(ctx context.Context) *StartedService { s.serviceStatus = &ServiceStatus{Status: ServiceStatus_STARTED} s.startedAt = time.Now() instance.urlTestHistoryStorage.AddUpdateHook(s.urlTestSubscriber) - if instance.clashServer != nil { - instance.clashServer.AddModeUpdateHook(s.clashModeSubscriber) + if instance.clashMode != nil { + instance.clashMode.AddUpdateHook(s.clashModeSubscriber) } - instance.logFactory.(log.ObservableFactory).AttachPlatformWriter(s) + instance.logFactory.AttachPlatformWriter(s) return s } diff --git a/daemon/instance.go b/daemon/instance.go index 44736e25..495b2b99 100644 --- a/daemon/instance.go +++ b/daemon/instance.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-box/common/trafficcontrol" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/clashmode" "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/locale" "github.com/sagernet/sing-box/log" @@ -25,7 +26,7 @@ type Instance struct { cancel context.CancelFunc instance *box.Box connectionManager adapter.ConnectionManager - clashServer adapter.ClashServer + clashMode *clashmode.Manager trafficManager *trafficcontrol.Manager cacheFile adapter.CacheFile pauseManager pause.Manager @@ -130,7 +131,7 @@ func (s *StartedService) newInstance(ctx context.Context, profileContent string, } i.instance = boxInstance i.connectionManager = service.FromContext[adapter.ConnectionManager](ctx) - i.clashServer = service.FromContext[adapter.ClashServer](ctx) + i.clashMode = service.PtrFromContext[clashmode.Manager](ctx) i.trafficManager = service.PtrFromContext[trafficcontrol.Manager](ctx) i.pauseManager = service.FromContext[pause.Manager](ctx) i.cacheFile = service.FromContext[adapter.CacheFile](ctx) @@ -145,7 +146,7 @@ func attachInstance(ctx context.Context) *Instance { return &Instance{ ctx: ctx, connectionManager: service.FromContext[adapter.ConnectionManager](ctx), - clashServer: service.FromContext[adapter.ClashServer](ctx), + clashMode: service.PtrFromContext[clashmode.Manager](ctx), trafficManager: service.PtrFromContext[trafficcontrol.Manager](ctx), pauseManager: service.FromContext[pause.Manager](ctx), cacheFile: service.FromContext[adapter.CacheFile](ctx), diff --git a/daemon/started_service.go b/daemon/started_service.go index 0ca8e4bb..c9f8f884 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -277,8 +277,8 @@ func (s *StartedService) StartOrReloadService(ctx context.Context, profileConten return err } instance.urlTestHistoryStorage.AddUpdateHook(s.urlTestSubscriber) - if instance.clashServer != nil { - instance.clashServer.AddModeUpdateHook(s.clashModeSubscriber) + if instance.clashMode != nil { + instance.clashMode.AddUpdateHook(s.clashModeSubscriber) } s.serviceAccess.Lock() s.instance = instance @@ -635,14 +635,14 @@ func (s *StartedService) GetClashModeStatus(ctx context.Context, empty *emptypb. s.serviceAccess.RUnlock() return nil, os.ErrInvalid } - clashServer := s.instance.clashServer + clashMode := s.instance.clashMode s.serviceAccess.RUnlock() - if clashServer == nil { + if clashMode == nil { return nil, status.Error(codes.NotFound, "clash mode not available") } return &ClashModeStatus{ - ModeList: clashServer.ModeList(), - CurrentMode: clashServer.Mode(), + ModeList: clashMode.ModeList(), + CurrentMode: clashMode.Mode(), }, nil } @@ -665,12 +665,12 @@ func (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.Se s.serviceAccess.RLock() var message *ClashMode if s.serviceStatus.Status == ServiceStatus_STARTED { - clashServer := s.instance.clashServer - if clashServer == nil { + clashMode := s.instance.clashMode + if clashMode == nil { s.serviceAccess.RUnlock() return status.Error(codes.NotFound, "clash mode not available") } - message = &ClashMode{Mode: clashServer.Mode()} + message = &ClashMode{Mode: clashMode.Mode()} } else { message = &ClashMode{} } @@ -700,12 +700,12 @@ func (s *StartedService) SetClashMode(ctx context.Context, request *ClashMode) ( s.serviceAccess.RUnlock() return nil, os.ErrInvalid } - clashServer := s.instance.clashServer + clashMode := s.instance.clashMode s.serviceAccess.RUnlock() - if clashServer == nil { + if clashMode == nil { return nil, status.Error(codes.NotFound, "clash mode not available") } - clashServer.SetMode(request.Mode) + clashMode.SetMode(request.Mode) return &emptypb.Empty{}, nil } diff --git a/experimental/clashapi.go b/experimental/clashapi.go index 4ad07c8b..2472f040 100644 --- a/experimental/clashapi.go +++ b/experimental/clashapi.go @@ -3,16 +3,13 @@ package experimental import ( "context" "os" - "sort" "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" ) -type ClashServerConstructor = func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) +type ClashServerConstructor = func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.LifecycleService, error) var clashServerConstructor ClashServerConstructor @@ -20,62 +17,9 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) { clashServerConstructor = constructor } -func NewClashServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewClashServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.LifecycleService, error) { if clashServerConstructor == nil { return nil, os.ErrInvalid } return clashServerConstructor(ctx, logFactory, options) } - -func CalculateClashModeList(options option.Options) []string { - var clashModes []string - clashModes = append(clashModes, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...) - clashModes = append(clashModes, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...) - clashModes = common.FilterNotDefault(common.Uniq(clashModes)) - predefinedOrder := []string{ - "Rule", "Global", "Direct", - } - var newClashModes []string - for _, mode := range clashModes { - if !common.Contains(predefinedOrder, mode) { - newClashModes = append(newClashModes, mode) - } - } - sort.Strings(newClashModes) - for _, mode := range predefinedOrder { - if common.Contains(clashModes, mode) { - newClashModes = append(newClashModes, mode) - } - } - return newClashModes -} - -func extraClashModeFromRule(rules []option.Rule) []string { - var clashMode []string - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if rule.DefaultOptions.ClashMode != "" { - clashMode = append(clashMode, rule.DefaultOptions.ClashMode) - } - case C.RuleTypeLogical: - clashMode = append(clashMode, extraClashModeFromRule(rule.LogicalOptions.Rules)...) - } - } - return clashMode -} - -func extraClashModeFromDNSRule(rules []option.DNSRule) []string { - var clashMode []string - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if rule.DefaultOptions.ClashMode != "" { - clashMode = append(clashMode, rule.DefaultOptions.ClashMode) - } - case C.RuleTypeLogical: - clashMode = append(clashMode, extraClashModeFromDNSRule(rule.LogicalOptions.Rules)...) - } - } - return clashMode -} diff --git a/experimental/clashapi/configs.go b/experimental/clashapi/configs.go index 8ae1d258..35a1d4ba 100644 --- a/experimental/clashapi/configs.go +++ b/experimental/clashapi/configs.go @@ -42,8 +42,8 @@ func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWrit logLevel = log.LevelError } render.JSON(w, r, &configSchema{ - Mode: server.mode, - ModeList: server.modeList, + Mode: server.clashMode.Mode(), + ModeList: server.clashMode.ModeList(), BindAddress: "*", LogLevel: log.FormatLevel(logLevel), }) @@ -60,7 +60,7 @@ func patchConfigs(server *Server) func(w http.ResponseWriter, r *http.Request) { return } if newConfig.Mode != "" { - server.SetMode(newConfig.Mode) + server.clashMode.SetMode(newConfig.Mode) } render.NoContent(w, r) } diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 8de33d6a..b9222ad5 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -9,7 +9,6 @@ import ( "os" "runtime" "strings" - "sync" "syscall" "time" @@ -19,12 +18,12 @@ import ( "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" + "github.com/sagernet/sing-box/experimental/clashmode" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" - "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" "github.com/sagernet/ws" @@ -38,7 +37,7 @@ func init() { experimental.RegisterClashServerConstructor(NewServer) } -var _ adapter.ClashServer = (*Server)(nil) +var _ adapter.LifecycleService = (*Server)(nil) type Server struct { ctx context.Context @@ -51,20 +50,16 @@ type Server struct { httpServer *http.Server trafficManager *trafficcontrol.Manager urlTestHistory *urltest.HistoryStorage + clashMode *clashmode.Manager logDebug bool - mode string - modeList []string - modeUpdateAccess sync.Mutex - modeUpdateHooks []*observable.Subscriber[struct{}] - externalController bool externalUI string externalUIDownloadURL string externalUIDownloadDetour string } -func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { +func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.LifecycleService, error) { trafficManager := service.PtrFromContext[trafficcontrol.Manager](ctx) if trafficManager == nil { return nil, E.New("missing traffic manager") @@ -73,6 +68,10 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op if urlTestHistory == nil { return nil, E.New("missing URL test history storage") } + clashMode := service.PtrFromContext[clashmode.Manager](ctx) + if clashMode == nil { + return nil, E.New("missing clash mode manager") + } chiRouter := chi.NewRouter() s := &Server{ ctx: ctx, @@ -88,20 +87,12 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op }, trafficManager: trafficManager, urlTestHistory: urlTestHistory, + clashMode: clashMode, logDebug: logFactory.Level() >= log.LevelDebug, - modeList: options.ModeList, externalController: options.ExternalController != "", externalUIDownloadURL: options.ExternalUIDownloadURL, externalUIDownloadDetour: options.ExternalUIDownloadDetour, } - defaultMode := "Rule" - if options.DefaultMode != "" { - defaultMode = options.DefaultMode - } - if !common.Contains(s.modeList, defaultMode) { - s.modeList = append([]string{defaultMode}, s.modeList...) - } - s.mode = defaultMode //goland:noinspection GoDeprecation //nolint:staticcheck if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" { @@ -157,45 +148,34 @@ func (s *Server) Name() string { } func (s *Server) Start(stage adapter.StartStage) error { - switch stage { - case adapter.StartStateStart: - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - mode := cacheFile.LoadMode() - if common.Any(s.modeList, func(it string) bool { - return strings.EqualFold(it, mode) - }) { - s.mode = mode - } - } - case adapter.StartStateStarted: - if s.externalController { - s.checkAndDownloadExternalUI() - var ( - listener net.Listener - err error - ) - for range 3 { - listener, err = net.Listen("tcp", s.httpServer.Addr) - if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { - time.Sleep(100 * time.Millisecond) - continue - } - break - } - if err != nil { - return E.Cause(err, "external controller listen error") - } - s.logger.Info("restful api listening at ", listener.Addr()) - go func() { - err = s.httpServer.Serve(listener) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - s.logger.Error("external controller serve error: ", err) - } - }() - } + if stage != adapter.StartStateStarted { + return nil + } + if s.externalController { + s.checkAndDownloadExternalUI() + var ( + listener net.Listener + err error + ) + for range 3 { + listener, err = net.Listen("tcp", s.httpServer.Addr) + if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) { + time.Sleep(100 * time.Millisecond) + continue + } + break + } + if err != nil { + return E.Cause(err, "external controller listen error") + } + s.logger.Info("restful api listening at ", listener.Addr()) + go func() { + err = s.httpServer.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error("external controller serve error: ", err) + } + }() } - return nil } @@ -205,49 +185,6 @@ func (s *Server) Close() error { ) } -func (s *Server) Mode() string { - return s.mode -} - -func (s *Server) ModeList() []string { - return s.modeList -} - -func (s *Server) AddModeUpdateHook(hook *observable.Subscriber[struct{}]) { - s.modeUpdateAccess.Lock() - defer s.modeUpdateAccess.Unlock() - s.modeUpdateHooks = append(s.modeUpdateHooks, hook) -} - -func (s *Server) SetMode(newMode string) { - if !common.Contains(s.modeList, newMode) { - newMode = common.Find(s.modeList, func(it string) bool { - return strings.EqualFold(it, newMode) - }) - } - if !common.Contains(s.modeList, newMode) { - return - } - if newMode == s.mode { - return - } - s.mode = newMode - s.modeUpdateAccess.Lock() - for _, hook := range s.modeUpdateHooks { - hook.Emit(struct{}{}) - } - s.modeUpdateAccess.Unlock() - s.dnsRouter.ClearCache() - cacheFile := service.FromContext[adapter.CacheFile](s.ctx) - if cacheFile != nil { - err := cacheFile.StoreMode(newMode) - if err != nil { - s.logger.Error(E.Cause(err, "save mode")) - } - } - s.logger.Info("updated mode: ", newMode) -} - func authentication(serverSecret string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { diff --git a/experimental/clashmode/manager.go b/experimental/clashmode/manager.go new file mode 100644 index 00000000..6491d754 --- /dev/null +++ b/experimental/clashmode/manager.go @@ -0,0 +1,109 @@ +package clashmode + +import ( + "context" + "strings" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/observable" + "github.com/sagernet/sing/service" +) + +type Manager struct { + ctx context.Context + logger log.Logger + dnsRouter adapter.DNSRouter + mode string + modeList []string + updateAccess sync.Mutex + updateHooks []*observable.Subscriber[struct{}] +} + +func NewManager(ctx context.Context, logger log.Logger, defaultMode string, modeList []string) *Manager { + if defaultMode == "" { + defaultMode = "Rule" + } + if !common.Contains(modeList, defaultMode) { + modeList = append([]string{defaultMode}, modeList...) + } + return &Manager{ + ctx: ctx, + logger: logger, + dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + mode: defaultMode, + modeList: modeList, + } +} + +func (m *Manager) Name() string { + return "clash mode manager" +} + +func (m *Manager) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + cacheFile := service.FromContext[adapter.CacheFile](m.ctx) + if cacheFile != nil { + mode := cacheFile.LoadMode() + if common.Any(m.modeList, func(it string) bool { + return strings.EqualFold(it, mode) + }) { + m.mode = mode + } + } + return nil +} + +func (m *Manager) Close() error { + return nil +} + +func (m *Manager) Mode() string { + return m.mode +} + +func (m *Manager) ModeList() []string { + return m.modeList +} + +func (m *Manager) AddUpdateHook(hook *observable.Subscriber[struct{}]) { + m.updateAccess.Lock() + defer m.updateAccess.Unlock() + m.updateHooks = append(m.updateHooks, hook) +} + +func (m *Manager) SetMode(newMode string) { + if !common.Contains(m.modeList, newMode) { + newMode = common.Find(m.modeList, func(it string) bool { + return strings.EqualFold(it, newMode) + }) + } + if !common.Contains(m.modeList, newMode) { + return + } + if newMode == m.mode { + return + } + m.mode = newMode + m.updateAccess.Lock() + for _, hook := range m.updateHooks { + hook.Emit(struct{}{}) + } + m.updateAccess.Unlock() + m.dnsRouter.ClearCache() + cacheFile := service.FromContext[adapter.CacheFile](m.ctx) + if cacheFile != nil { + err := cacheFile.StoreMode(newMode) + if err != nil { + m.logger.Error(E.Cause(err, "save mode")) + } + } + m.logger.Info("updated mode: ", newMode) +} + +var _ adapter.LifecycleService = (*Manager)(nil) diff --git a/experimental/clashmode/mode_list.go b/experimental/clashmode/mode_list.go new file mode 100644 index 00000000..e0c283e1 --- /dev/null +++ b/experimental/clashmode/mode_list.go @@ -0,0 +1,62 @@ +package clashmode + +import ( + "sort" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" +) + +func CalculateModeList(options option.Options) []string { + var clashModes []string + clashModes = append(clashModes, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...) + clashModes = append(clashModes, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...) + clashModes = common.FilterNotDefault(common.Uniq(clashModes)) + predefinedOrder := []string{ + "Rule", "Global", "Direct", + } + var newClashModes []string + for _, mode := range clashModes { + if !common.Contains(predefinedOrder, mode) { + newClashModes = append(newClashModes, mode) + } + } + sort.Strings(newClashModes) + for _, mode := range predefinedOrder { + if common.Contains(clashModes, mode) { + newClashModes = append(newClashModes, mode) + } + } + return newClashModes +} + +func extraClashModeFromRule(rules []option.Rule) []string { + var clashMode []string + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if rule.DefaultOptions.ClashMode != "" { + clashMode = append(clashMode, rule.DefaultOptions.ClashMode) + } + case C.RuleTypeLogical: + clashMode = append(clashMode, extraClashModeFromRule(rule.LogicalOptions.Rules)...) + } + } + return clashMode +} + +func extraClashModeFromDNSRule(rules []option.DNSRule) []string { + var clashMode []string + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if rule.DefaultOptions.ClashMode != "" { + clashMode = append(clashMode, rule.DefaultOptions.ClashMode) + } + case C.RuleTypeLogical: + clashMode = append(clashMode, extraClashModeFromDNSRule(rule.LogicalOptions.Rules)...) + } + } + return clashMode +} diff --git a/include/clashapi_stub.go b/include/clashapi_stub.go index e7d5304f..53f78148 100644 --- a/include/clashapi_stub.go +++ b/include/clashapi_stub.go @@ -13,7 +13,7 @@ import ( ) func init() { - experimental.RegisterClashServerConstructor(func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { + experimental.RegisterClashServerConstructor(func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.LifecycleService, error) { return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`) }) } diff --git a/log/factory.go b/log/factory.go index b73fb236..9c1921a3 100644 --- a/log/factory.go +++ b/log/factory.go @@ -17,12 +17,12 @@ type Factory interface { SetLevel(level Level) Logger() ContextLogger NewLogger(tag string) ContextLogger + AttachPlatformWriter(writer PlatformWriter) } type ObservableFactory interface { Factory observable.Observable[Entry] - AttachPlatformWriter(writer PlatformWriter) } type Entry struct { diff --git a/option/experimental.go b/option/experimental.go index 3259e6a8..5cf6f51b 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -26,7 +26,6 @@ type ClashAPIOptions struct { ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty" reference:"outbound"` Secret string `json:"secret,omitempty"` DefaultMode string `json:"default_mode,omitempty"` - ModeList []string `json:"-"` AccessControlAllowOrigin badoption.Listable[string] `json:"access_control_allow_origin,omitempty"` AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` diff --git a/route/rule/rule_item_clash_mode.go b/route/rule/rule_item_clash_mode.go index fe2347a0..799ae06d 100644 --- a/route/rule/rule_item_clash_mode.go +++ b/route/rule/rule_item_clash_mode.go @@ -5,15 +5,16 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental/clashmode" "github.com/sagernet/sing/service" ) var _ RuleItem = (*ClashModeItem)(nil) type ClashModeItem struct { - ctx context.Context - clashServer adapter.ClashServer - mode string + ctx context.Context + clashMode *clashmode.Manager + mode string } func NewClashModeItem(ctx context.Context, mode string) *ClashModeItem { @@ -24,15 +25,15 @@ func NewClashModeItem(ctx context.Context, mode string) *ClashModeItem { } func (r *ClashModeItem) Start() error { - r.clashServer = service.FromContext[adapter.ClashServer](r.ctx) + r.clashMode = service.PtrFromContext[clashmode.Manager](r.ctx) return nil } func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool { - if r.clashServer == nil { + if r.clashMode == nil { return false } - return strings.EqualFold(r.clashServer.Mode(), r.mode) + return strings.EqualFold(r.clashMode.Mode(), r.mode) } func (r *ClashModeItem) String() string {