diff --git a/adapter/experimental.go b/adapter/experimental.go index 9afdcb7b..697fa9f1 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -12,6 +12,7 @@ type ClashServer interface { Service PreStarter Mode() string + ModeList() []string StoreSelected() bool StoreFakeIP() bool CacheFile() ClashCacheFile @@ -21,6 +22,8 @@ type ClashServer interface { } type ClashCacheFile interface { + LoadMode() string + StoreMode(mode string) error LoadSelected(group string) string StoreSelected(group string, selected string) error LoadGroupExpand(group string) (isExpand bool, loaded bool) diff --git a/adapter/router.go b/adapter/router.go index df74ee0a..ca6cc3a3 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -32,6 +32,7 @@ type Router interface { Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) + ClearDNSCache() InterfaceFinder() control.InterfaceFinder UpdateInterfaces() error diff --git a/box.go b/box.go index baebc761..73a400b7 100644 --- a/box.go +++ b/box.go @@ -145,7 +145,9 @@ func New(options Options) (*Box, error) { preServices := make(map[string]adapter.Service) postServices := make(map[string]adapter.Service) if needClashAPI { - clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(experimentalOptions.ClashAPI)) + clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) + clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) + clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions) if err != nil { return nil, E.Cause(err, "create clash api server") } diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index 8dd85f51..c735d135 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -10,7 +10,6 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/x/list" ) type History struct { @@ -21,7 +20,7 @@ type History struct { type HistoryStorage struct { access sync.RWMutex delayHistory map[string]*History - callbacks list.List[func()] + updateHook chan<- struct{} } func NewHistoryStorage() *HistoryStorage { @@ -30,16 +29,8 @@ func NewHistoryStorage() *HistoryStorage { } } -func (s *HistoryStorage) AddListener(listener func()) *list.Element[func()] { - s.access.Lock() - defer s.access.Unlock() - return s.callbacks.PushBack(listener) -} - -func (s *HistoryStorage) RemoveListener(element *list.Element[func()]) { - s.access.Lock() - defer s.access.Unlock() - s.callbacks.Remove(element) +func (s *HistoryStorage) SetHook(hook chan<- struct{}) { + s.updateHook = hook } func (s *HistoryStorage) LoadURLTestHistory(tag string) *History { @@ -66,13 +57,20 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) { } func (s *HistoryStorage) notifyUpdated() { - s.access.RLock() - defer s.access.RUnlock() - for element := s.callbacks.Front(); element != nil; element = element.Next() { - element.Value() + updateHook := s.updateHook + if updateHook != nil { + select { + case updateHook <- struct{}{}: + default: + } } } +func (s *HistoryStorage) Close() error { + s.updateHook = nil + return nil +} + func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) { if link == "" { link = "https://www.gstatic.com/generate_204" diff --git a/docs/configuration/experimental/index.md b/docs/configuration/experimental/index.md index 0f9a09ec..c27302a9 100644 --- a/docs/configuration/experimental/index.md +++ b/docs/configuration/experimental/index.md @@ -12,7 +12,9 @@ "external_ui_download_detour": "", "secret": "", "default_mode": "", + "store_mode": false, "store_selected": false, + "store_fakeip": false, "cache_file": "", "cache_id": "" }, @@ -80,6 +82,10 @@ Default mode in clash, `rule` will be used if empty. This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item. +#### store_mode + +Store Clash mode in cache file. + #### store_selected !!! note "" @@ -88,6 +94,10 @@ This setting has no direct effect, but can be used in routing and DNS rules via Store selected outbound for the `Selector` outbound in cache file. +#### store_fakeip + +Store fakeip in cache file. + #### cache_file Cache file path, `cache.db` will be used if empty. diff --git a/docs/configuration/experimental/index.zh.md b/docs/configuration/experimental/index.zh.md index d9999388..ae2d7fb6 100644 --- a/docs/configuration/experimental/index.zh.md +++ b/docs/configuration/experimental/index.zh.md @@ -12,7 +12,9 @@ "external_ui_download_detour": "", "secret": "", "default_mode": "", + "store_mode": false, "store_selected": false, + "store_fakeip": false, "cache_file": "", "cache_id": "" }, @@ -78,6 +80,10 @@ Clash 中的默认模式,默认使用 `rule`。 此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。 +#### store_mode + +将 Clash 模式存储在缓存文件中。 + #### store_selected !!! note "" @@ -86,6 +92,10 @@ Clash 中的默认模式,默认使用 `rule`。 将 `Selector` 中出站的选定的目标出站存储在缓存文件中。 +#### store_fakeip + +将 fakeip 存储在缓存文件中。 + #### cache_file 缓存文件路径,默认使用`cache.db`。 diff --git a/experimental/clashapi.go b/experimental/clashapi.go index c2b5eac7..894d40a7 100644 --- a/experimental/clashapi.go +++ b/experimental/clashapi.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" ) type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) @@ -23,3 +24,28 @@ func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.O } return clashServerConstructor(ctx, router, logFactory, options) } + +func CalculateClashModeList(options option.Options) []string { + var clashMode []string + for _, dnsRule := range common.PtrValueOrDefault(options.DNS).Rules { + if dnsRule.DefaultOptions.ClashMode != "" && !common.Contains(clashMode, dnsRule.DefaultOptions.ClashMode) { + clashMode = append(clashMode, dnsRule.DefaultOptions.ClashMode) + } + for _, defaultRule := range dnsRule.LogicalOptions.Rules { + if defaultRule.ClashMode != "" && !common.Contains(clashMode, defaultRule.ClashMode) { + clashMode = append(clashMode, defaultRule.ClashMode) + } + } + } + for _, rule := range common.PtrValueOrDefault(options.Route).Rules { + if rule.DefaultOptions.ClashMode != "" && !common.Contains(clashMode, rule.DefaultOptions.ClashMode) { + clashMode = append(clashMode, rule.DefaultOptions.ClashMode) + } + for _, defaultRule := range rule.LogicalOptions.Rules { + if defaultRule.ClashMode != "" && !common.Contains(clashMode, defaultRule.ClashMode) { + clashMode = append(clashMode, defaultRule.ClashMode) + } + } + } + return clashMode +} diff --git a/experimental/clashapi/cachefile/cache.go b/experimental/clashapi/cachefile/cache.go index e135a742..f7355562 100644 --- a/experimental/clashapi/cachefile/cache.go +++ b/experimental/clashapi/cachefile/cache.go @@ -8,6 +8,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" "go.etcd.io/bbolt" ) @@ -15,6 +16,15 @@ import ( var ( bucketSelected = []byte("selected") bucketExpand = []byte("group_expand") + bucketMode = []byte("clash_mode") + + bucketNameList = []string{ + string(bucketSelected), + string(bucketExpand), + string(bucketMode), + } + + cacheIDDefault = []byte("default") ) var _ adapter.ClashCacheFile = (*CacheFile)(nil) @@ -52,14 +62,14 @@ func Open(path string, cacheID string) (*CacheFile, error) { if name[0] == 0 { return b.ForEachBucket(func(k []byte) error { bucketName := string(k) - if !(bucketName == string(bucketSelected) || bucketName == string(bucketExpand)) { + if !(common.Contains(bucketNameList, bucketName)) { _ = b.DeleteBucket(name) } return nil }) } else { bucketName := string(name) - if !(bucketName == string(bucketSelected) || bucketName == string(bucketExpand) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) { + if !(common.Contains(bucketNameList, bucketName) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) { _ = tx.DeleteBucket(name) } } @@ -78,6 +88,39 @@ func Open(path string, cacheID string) (*CacheFile, error) { }, nil } +func (c *CacheFile) LoadMode() string { + var mode string + c.DB.View(func(t *bbolt.Tx) error { + bucket := t.Bucket(bucketMode) + if bucket == nil { + return nil + } + var modeBytes []byte + if len(c.cacheID) > 0 { + modeBytes = bucket.Get(c.cacheID) + } else { + modeBytes = bucket.Get(cacheIDDefault) + } + mode = string(modeBytes) + return nil + }) + return mode +} + +func (c *CacheFile) StoreMode(mode string) error { + return c.DB.Batch(func(t *bbolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketMode) + if err != nil { + return err + } + if len(c.cacheID) > 0 { + return bucket.Put(c.cacheID, []byte(mode)) + } else { + return bucket.Put(cacheIDDefault, []byte(mode)) + } + }) +} + func (c *CacheFile) bucket(t *bbolt.Tx, key []byte) *bbolt.Bucket { if c.cacheID == nil { return t.Bucket(key) diff --git a/experimental/clashapi/configs.go b/experimental/clashapi/configs.go index bdb1aa1c..9d1e6109 100644 --- a/experimental/clashapi/configs.go +++ b/experimental/clashapi/configs.go @@ -2,7 +2,6 @@ package clashapi import ( "net/http" - "strings" "github.com/sagernet/sing-box/log" @@ -10,11 +9,11 @@ import ( "github.com/go-chi/render" ) -func configRouter(server *Server, logFactory log.Factory, logger log.Logger) http.Handler { +func configRouter(server *Server, logFactory log.Factory) http.Handler { r := chi.NewRouter() r.Get("/", getConfigs(server, logFactory)) r.Put("/", updateConfigs) - r.Patch("/", patchConfigs(server, logger)) + r.Patch("/", patchConfigs(server)) return r } @@ -48,7 +47,7 @@ func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWrit } } -func patchConfigs(server *Server, logger log.Logger) func(w http.ResponseWriter, r *http.Request) { +func patchConfigs(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var newConfig configSchema err := render.DecodeJSON(r.Body, &newConfig) @@ -58,11 +57,7 @@ func patchConfigs(server *Server, logger log.Logger) func(w http.ResponseWriter, return } if newConfig.Mode != "" { - mode := strings.ToLower(newConfig.Mode) - if server.mode != mode { - server.mode = mode - logger.Info("updated mode: ", mode) - } + server.SetMode(newConfig.Mode) } render.NoContent(w, r) } diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index c8a55c58..f9dacb5d 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -46,6 +46,9 @@ type Server struct { trafficManager *trafficontrol.Manager urlTestHistory *urltest.HistoryStorage mode string + modeList []string + modeUpdateHook chan<- struct{} + storeMode bool storeSelected bool storeFakeIP bool cacheFilePath string @@ -70,9 +73,10 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ Handler: chiRouter, }, trafficManager: trafficManager, - mode: strings.ToLower(options.DefaultMode), - storeSelected: options.StoreSelected, + modeList: options.ModeList, externalController: options.ExternalController != "", + storeMode: options.StoreMode, + storeSelected: options.StoreSelected, storeFakeIP: options.StoreFakeIP, externalUIDownloadURL: options.ExternalUIDownloadURL, externalUIDownloadDetour: options.ExternalUIDownloadDetour, @@ -81,10 +85,15 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ if server.urlTestHistory == nil { server.urlTestHistory = urltest.NewHistoryStorage() } - if server.mode == "" { - server.mode = "rule" + defaultMode := "Rule" + if options.DefaultMode != "" { + defaultMode = options.DefaultMode } - if options.StoreSelected || options.StoreFakeIP || options.ExternalController == "" { + if !common.Contains(server.modeList, defaultMode) { + server.modeList = append(server.modeList, defaultMode) + } + server.mode = defaultMode + if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.ExternalController == "" { cachePath := os.ExpandEnv(options.CacheFile) if cachePath == "" { cachePath = "cache.db" @@ -110,7 +119,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ r.Get("/logs", getLogs(logFactory)) r.Get("/traffic", traffic(trafficManager)) r.Get("/version", version) - r.Mount("/configs", configRouter(server, logFactory, server.logger)) + r.Mount("/configs", configRouter(server, logFactory)) r.Mount("/proxies", proxyRouter(server, router)) r.Mount("/rules", ruleRouter(router)) r.Mount("/connections", connectionRouter(router, trafficManager)) @@ -143,6 +152,14 @@ func (s *Server) PreStart() error { return E.Cause(err, "open cache file") } s.cacheFile = cacheFile + if s.storeMode { + mode := s.cacheFile.LoadMode() + if common.Any(s.modeList, func(it string) bool { + return strings.EqualFold(it, mode) + }) { + s.mode = mode + } + } } return nil } @@ -170,6 +187,7 @@ func (s *Server) Close() error { common.PtrOrNil(s.httpServer), s.trafficManager, s.cacheFile, + s.urlTestHistory, ) } @@ -177,6 +195,43 @@ func (s *Server) Mode() string { return s.mode } +func (s *Server) ModeList() []string { + return s.modeList +} + +func (s *Server) SetModeUpdateHook(hook chan<- struct{}) { + s.modeUpdateHook = 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 + if s.modeUpdateHook != nil { + select { + case s.modeUpdateHook <- struct{}{}: + default: + } + } + s.router.ClearDNSCache() + if s.storeMode { + err := s.cacheFile.StoreMode(newMode) + if err != nil { + s.logger.Error(E.Cause(err, "save mode")) + } + } + s.logger.Info("updated mode: ", newMode) +} + func (s *Server) StoreSelected() bool { return s.storeSelected } diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go index 7824a87d..60da6ab2 100644 --- a/experimental/libbox/command.go +++ b/experimental/libbox/command.go @@ -9,4 +9,6 @@ const ( CommandSelectOutbound CommandURLTest CommandGroupExpand + CommandClashMode + CommandSetClashMode ) diff --git a/experimental/libbox/command_clash_mode.go b/experimental/libbox/command_clash_mode.go new file mode 100644 index 00000000..0f850ea4 --- /dev/null +++ b/experimental/libbox/command_clash_mode.go @@ -0,0 +1,135 @@ +package libbox + +import ( + "encoding/binary" + "io" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental/clashapi" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/rw" +) + +func (c *CommandClient) SetClashMode(newMode string) error { + conn, err := c.directConnect() + if err != nil { + return err + } + defer conn.Close() + err = binary.Write(conn, binary.BigEndian, uint8(CommandSetClashMode)) + if err != nil { + return err + } + err = rw.WriteVString(conn, newMode) + if err != nil { + return err + } + return readError(conn) +} + +func (s *CommandServer) handleSetClashMode(conn net.Conn) error { + defer conn.Close() + newMode, err := rw.ReadVString(conn) + if err != nil { + return err + } + service := s.service + if service == nil { + return writeError(conn, E.New("service not ready")) + } + clashServer := service.instance.Router().ClashServer() + if clashServer == nil { + return writeError(conn, E.New("Clash API disabled")) + } + clashServer.(*clashapi.Server).SetMode(newMode) + return writeError(conn, nil) +} + +func (c *CommandClient) handleModeConn(conn net.Conn) { + defer conn.Close() + + for { + newMode, err := rw.ReadVString(conn) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.UpdateClashMode(newMode) + } +} + +func (s *CommandServer) handleModeConn(conn net.Conn) error { + defer conn.Close() + ctx := connKeepAlive(conn) + for s.service == nil { + select { + case <-time.After(time.Second): + continue + case <-ctx.Done(): + return ctx.Err() + } + } + clashServer := s.service.instance.Router().ClashServer() + if clashServer == nil { + defer conn.Close() + return binary.Write(conn, binary.BigEndian, uint16(0)) + } + err := writeClashModeList(conn, clashServer) + if err != nil { + return err + } + for { + select { + case <-s.modeUpdate: + err = rw.WriteVString(conn, clashServer.Mode()) + if err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func readClashModeList(reader io.Reader) (modeList []string, currentMode string, err error) { + var modeListLength uint16 + err = binary.Read(reader, binary.BigEndian, &modeListLength) + if err != nil { + return + } + if modeListLength == 0 { + return + } + modeList = make([]string, modeListLength) + for i := 0; i < int(modeListLength); i++ { + modeList[i], err = rw.ReadVString(reader) + if err != nil { + return + } + } + currentMode, err = rw.ReadVString(reader) + return +} + +func writeClashModeList(writer io.Writer, clashServer adapter.ClashServer) error { + modeList := clashServer.ModeList() + err := binary.Write(writer, binary.BigEndian, uint16(len(modeList))) + if err != nil { + return err + } + if len(modeList) > 0 { + for _, mode := range modeList { + err = rw.WriteVString(writer, mode) + if err != nil { + return err + } + } + err = rw.WriteVString(writer, clashServer.Mode()) + if err != nil { + return err + } + } + return nil +} diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index 720ffe62..e74c606b 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -3,6 +3,7 @@ package libbox import ( "encoding/binary" "net" + "os" "path/filepath" "github.com/sagernet/sing/common" @@ -26,6 +27,8 @@ type CommandClientHandler interface { WriteLog(message string) WriteStatus(message *StatusMessage) WriteGroups(message OutboundGroupIterator) + InitializeClashMode(modeList StringIterator, currentMode string) + UpdateClashMode(newMode string) } func NewStandaloneCommandClient() *CommandClient { @@ -79,6 +82,23 @@ func (c *CommandClient) Connect() error { } c.handler.Connected() go c.handleGroupConn(conn) + case CommandClashMode: + var ( + modeList []string + currentMode string + ) + modeList, currentMode, err = readClashModeList(conn) + if err != nil { + return err + } + c.handler.Connected() + c.handler.InitializeClashMode(newIterator(modeList), currentMode) + if len(modeList) == 0 { + conn.Close() + c.handler.Disconnected(os.ErrInvalid.Error()) + return nil + } + go c.handleModeConn(conn) } return nil } diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index a65fa1d3..93482088 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -199,6 +199,9 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { } group.items = append(group.items, &item) } + if len(group.items) < 2 { + continue + } groups = append(groups, group) } diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index ba2d573e..5e7214fd 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/sagernet/sing-box/common/urltest" + "github.com/sagernet/sing-box/experimental/clashapi" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/debug" @@ -28,8 +29,8 @@ type CommandServer struct { observer *observable.Observer[string] service *BoxService - urlTestListener *list.Element[func()] - urlTestUpdate chan struct{} + urlTestUpdate chan struct{} + modeUpdate chan struct{} } type CommandServerHandler interface { @@ -43,20 +44,18 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ maxLines: int(maxLines), subscriber: observable.NewSubscriber[string](128), urlTestUpdate: make(chan struct{}, 1), + modeUpdate: make(chan struct{}, 1), } server.observer = observable.NewObserver[string](server.subscriber, 64) return server } func (s *CommandServer) SetService(newService *BoxService) { - if s.service != nil && s.listener != nil { - service.PtrFromContext[urltest.HistoryStorage](s.service.ctx).RemoveListener(s.urlTestListener) - s.urlTestListener = nil + if newService != nil { + service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) + newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) } s.service = newService - if newService != nil { - s.urlTestListener = service.PtrFromContext[urltest.HistoryStorage](newService.ctx).AddListener(s.notifyURLTestUpdate) - } s.notifyURLTestUpdate() } @@ -156,6 +155,10 @@ func (s *CommandServer) handleConnection(conn net.Conn) error { return s.handleURLTest(conn) case CommandGroupExpand: return s.handleSetGroupExpand(conn) + case CommandClashMode: + return s.handleModeConn(conn) + case CommandSetClashMode: + return s.handleSetClashMode(conn) default: return E.New("unknown command: ", command) } diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index 7e100f9a..fcdaaa92 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -49,6 +49,9 @@ func (p *platformLocalDNSTransport) Start() error { return nil } +func (p *platformLocalDNSTransport) Reset() { +} + func (p *platformLocalDNSTransport) Close() error { return nil } diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 04d73730..b7418bd2 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -19,6 +19,7 @@ type PlatformInterface interface { UsePlatformInterfaceGetter() bool GetInterfaces() (NetworkInterfaceIterator, error) UnderNetworkExtension() bool + ClearDNSCache() } type TunInterface interface { diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index e99d842d..77afa17b 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -23,6 +23,7 @@ type Interface interface { UsePlatformInterfaceGetter() bool Interfaces() ([]NetworkInterface, error) UnderNetworkExtension() bool + ClearDNSCache() process.Searcher io.Writer } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index a24a0e87..6c98c179 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -25,10 +25,11 @@ import ( ) type BoxService struct { - ctx context.Context - cancel context.CancelFunc - instance *box.Box - pauseManager pause.Manager + ctx context.Context + cancel context.CancelFunc + instance *box.Box + pauseManager pause.Manager + urlTestHistoryStorage *urltest.HistoryStorage } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { @@ -39,9 +40,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box runtimeDebug.FreeOSMemory() ctx, cancel := context.WithCancel(context.Background()) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) - ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage()) - sleepManager := pause.NewDefaultManager(ctx) - ctx = pause.ContextWithManager(ctx, sleepManager) + urlTestHistoryStorage := urltest.NewHistoryStorage() + ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) + pauseManager := pause.NewDefaultManager(ctx) + ctx = pause.ContextWithManager(ctx, pauseManager) instance, err := box.New(box.Options{ Context: ctx, Options: options, @@ -53,10 +55,11 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box } runtimeDebug.FreeOSMemory() return &BoxService{ - ctx: ctx, - cancel: cancel, - instance: instance, - pauseManager: sleepManager, + ctx: ctx, + cancel: cancel, + instance: instance, + urlTestHistoryStorage: urlTestHistoryStorage, + pauseManager: pauseManager, }, nil } @@ -66,6 +69,7 @@ func (s *BoxService) Start() error { func (s *BoxService) Close() error { s.cancel() + s.urlTestHistoryStorage.Close() return s.instance.Close() } @@ -194,3 +198,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, er func (w *platformInterfaceWrapper) UnderNetworkExtension() bool { return w.iif.UnderNetworkExtension() } + +func (w *platformInterfaceWrapper) ClearDNSCache() { + w.iif.ClearDNSCache() +} diff --git a/go.mod b/go.mod index 9662c9f4..3494a59e 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,8 @@ require ( github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 github.com/sagernet/quic-go v0.0.0-20230824033040-30ef72e3be3e github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d - github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 + github.com/sagernet/sing v0.2.10-0.20230824115837-8d731e68853a + github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1 github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 diff --git a/go.sum b/go.sum index 5751c0d8..90057074 100644 --- a/go.sum +++ b/go.sum @@ -113,10 +113,10 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d h1:4kgoOCE48CuQcBUcoRnE0QTPXkl8yM8i7Nipmzp/978= -github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= -github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= -github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= +github.com/sagernet/sing v0.2.10-0.20230824115837-8d731e68853a h1:eV4HEz9NP7eAlQ/IHD6OF2VVM6ke4Vw6htuSAsvgtDk= +github.com/sagernet/sing v0.2.10-0.20230824115837-8d731e68853a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1 h1:5w+jXz8y/8UQAxO74TjftN5okYkpg5mGvVxXunlKdqI= +github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1/go.mod h1:Kg98PBJEg/08jsNFtmZWmPomhskn9Ausn50ecNm4M+8= github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I= github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY= diff --git a/option/clash.go b/option/clash.go index a0ba8a80..175a2c50 100644 --- a/option/clash.go +++ b/option/clash.go @@ -7,10 +7,13 @@ type ClashAPIOptions struct { ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"` Secret string `json:"secret,omitempty"` DefaultMode string `json:"default_mode,omitempty"` + StoreMode bool `json:"store_mode,omitempty"` StoreSelected bool `json:"store_selected,omitempty"` StoreFakeIP bool `json:"store_fakeip,omitempty"` CacheFile string `json:"cache_file,omitempty"` CacheID string `json:"cache_id,omitempty"` + + ModeList []string `json:"-"` } type SelectorOutboundOptions struct { diff --git a/route/router.go b/route/router.go index f2926f87..65bd981c 100644 --- a/route/router.go +++ b/route/router.go @@ -1005,14 +1005,7 @@ func (r *Router) notifyNetworkUpdate(event int) { } } - conntrack.Close() - - for _, outbound := range r.outbounds { - listener, isListener := outbound.(adapter.InterfaceUpdateListener) - if isListener { - listener.InterfaceUpdated() - } - } + r.ResetNetwork() return } @@ -1025,5 +1018,9 @@ func (r *Router) ResetNetwork() error { listener.InterfaceUpdated() } } + + for _, transport := range r.transports { + transport.Reset() + } return nil } diff --git a/route/router_dns.go b/route/router_dns.go index 0fe37352..3c76e999 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -146,6 +146,13 @@ func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr return r.Lookup(ctx, domain, dns.DomainStrategyAsIS) } +func (r *Router) ClearDNSCache() { + r.dnsClient.ClearCache() + if r.platformInterface != nil { + r.platformInterface.ClearDNSCache() + } +} + func LogDNSAnswers(logger log.ContextLogger, ctx context.Context, domain string, answers []mDNS.RR) { for _, answer := range answers { logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String())) diff --git a/route/rule_item_clash_mode.go b/route/rule_item_clash_mode.go index 888577e4..70141f11 100644 --- a/route/rule_item_clash_mode.go +++ b/route/rule_item_clash_mode.go @@ -16,7 +16,7 @@ type ClashModeItem struct { func NewClashModeItem(router adapter.Router, mode string) *ClashModeItem { return &ClashModeItem{ router: router, - mode: strings.ToLower(mode), + mode: mode, } } @@ -25,7 +25,7 @@ func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool { if clashServer == nil { return false } - return clashServer.Mode() == r.mode + return strings.EqualFold(clashServer.Mode(), r.mode) } func (r *ClashModeItem) String() string { diff --git a/test/go.mod b/test/go.mod index f7696161..6a166317 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v24.0.5+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid/v5 v5.0.0 - github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d + github.com/sagernet/sing v0.2.10-0.20230824115837-8d731e68853a github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 github.com/spyzhov/ajson v0.9.0 @@ -72,7 +72,7 @@ require ( github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/sagernet/quic-go v0.0.0-20230824033040-30ef72e3be3e // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect - github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 // indirect + github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1 // indirect github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 // indirect diff --git a/test/go.sum b/test/go.sum index 0fff83e0..3e648fae 100644 --- a/test/go.sum +++ b/test/go.sum @@ -131,8 +131,10 @@ github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2 github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d h1:4kgoOCE48CuQcBUcoRnE0QTPXkl8yM8i7Nipmzp/978= github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing v0.2.10-0.20230824115837-8d731e68853a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= +github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1/go.mod h1:Kg98PBJEg/08jsNFtmZWmPomhskn9Ausn50ecNm4M+8= github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I= github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY= diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index 35e8783f..85c7a0d6 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -85,6 +85,9 @@ func (t *Transport) Start() error { return nil } +func (t *Transport) Reset() { +} + func (t *Transport) Close() error { if t.interfaceCallback != nil { t.router.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) diff --git a/transport/fakeip/server.go b/transport/fakeip/server.go index 1a1d5916..40149aa4 100644 --- a/transport/fakeip/server.go +++ b/transport/fakeip/server.go @@ -54,6 +54,9 @@ func (s *Transport) Start() error { return nil } +func (s *Transport) Reset() { +} + func (s *Transport) Close() error { return nil }