Separate Clash mode management from Clash API server

This commit is contained in:
世界
2026-09-01 22:39:21 +08:00
parent 6c4589c933
commit a556d49491
14 changed files with 252 additions and 205 deletions
-9
View File
@@ -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"`
+12 -7
View File
@@ -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 {
+3 -5
View File
@@ -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
}
+4 -3
View File
@@ -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),
+12 -12
View File
@@ -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
}
+2 -58
View File
@@ -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
}
+3 -3
View File
@@ -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)
}
+36 -99
View File
@@ -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) {
+109
View File
@@ -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)
+62
View File
@@ -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
}
+1 -1
View File
@@ -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`)
})
}
+1 -1
View File
@@ -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 {
-1
View File
@@ -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"`
+7 -6
View File
@@ -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 {