mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-16 16:22:22 +08:00
Add store_mode
and platform Clash mode selector
This commit is contained in:
parent
6dcacf3b5e
commit
43f72a6419
|
@ -12,6 +12,7 @@ type ClashServer interface {
|
||||||
Service
|
Service
|
||||||
PreStarter
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
|
ModeList() []string
|
||||||
StoreSelected() bool
|
StoreSelected() bool
|
||||||
StoreFakeIP() bool
|
StoreFakeIP() bool
|
||||||
CacheFile() ClashCacheFile
|
CacheFile() ClashCacheFile
|
||||||
|
@ -21,6 +22,8 @@ type ClashServer interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClashCacheFile interface {
|
type ClashCacheFile interface {
|
||||||
|
LoadMode() string
|
||||||
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
||||||
|
|
|
@ -32,6 +32,7 @@ type Router interface {
|
||||||
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
||||||
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||||
|
ClearDNSCache()
|
||||||
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
UpdateInterfaces() error
|
UpdateInterfaces() error
|
||||||
|
|
4
box.go
4
box.go
|
@ -145,7 +145,9 @@ func New(options Options) (*Box, error) {
|
||||||
preServices := make(map[string]adapter.Service)
|
preServices := make(map[string]adapter.Service)
|
||||||
postServices := make(map[string]adapter.Service)
|
postServices := make(map[string]adapter.Service)
|
||||||
if needClashAPI {
|
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 {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type History struct {
|
type History struct {
|
||||||
|
@ -21,7 +20,7 @@ type History struct {
|
||||||
type HistoryStorage struct {
|
type HistoryStorage struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
delayHistory map[string]*History
|
delayHistory map[string]*History
|
||||||
callbacks list.List[func()]
|
updateHook chan<- struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistoryStorage() *HistoryStorage {
|
func NewHistoryStorage() *HistoryStorage {
|
||||||
|
@ -30,16 +29,8 @@ func NewHistoryStorage() *HistoryStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) AddListener(listener func()) *list.Element[func()] {
|
func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
|
||||||
s.access.Lock()
|
s.updateHook = hook
|
||||||
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) LoadURLTestHistory(tag string) *History {
|
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
||||||
|
@ -66,13 +57,20 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) notifyUpdated() {
|
func (s *HistoryStorage) notifyUpdated() {
|
||||||
s.access.RLock()
|
updateHook := s.updateHook
|
||||||
defer s.access.RUnlock()
|
if updateHook != nil {
|
||||||
for element := s.callbacks.Front(); element != nil; element = element.Next() {
|
select {
|
||||||
element.Value()
|
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) {
|
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||||
if link == "" {
|
if link == "" {
|
||||||
link = "https://www.gstatic.com/generate_204"
|
link = "https://www.gstatic.com/generate_204"
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
"external_ui_download_detour": "",
|
"external_ui_download_detour": "",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "",
|
"default_mode": "",
|
||||||
|
"store_mode": false,
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
|
"store_fakeip": false,
|
||||||
"cache_file": "",
|
"cache_file": "",
|
||||||
"cache_id": ""
|
"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.
|
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
|
#### store_selected
|
||||||
|
|
||||||
!!! note ""
|
!!! 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 selected outbound for the `Selector` outbound in cache file.
|
||||||
|
|
||||||
|
#### store_fakeip
|
||||||
|
|
||||||
|
Store fakeip in cache file.
|
||||||
|
|
||||||
#### cache_file
|
#### cache_file
|
||||||
|
|
||||||
Cache file path, `cache.db` will be used if empty.
|
Cache file path, `cache.db` will be used if empty.
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
"external_ui_download_detour": "",
|
"external_ui_download_detour": "",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "",
|
"default_mode": "",
|
||||||
|
"store_mode": false,
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
|
"store_fakeip": false,
|
||||||
"cache_file": "",
|
"cache_file": "",
|
||||||
"cache_id": ""
|
"cache_id": ""
|
||||||
},
|
},
|
||||||
|
@ -78,6 +80,10 @@ Clash 中的默认模式,默认使用 `rule`。
|
||||||
|
|
||||||
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
|
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
|
||||||
|
|
||||||
|
#### store_mode
|
||||||
|
|
||||||
|
将 Clash 模式存储在缓存文件中。
|
||||||
|
|
||||||
#### store_selected
|
#### store_selected
|
||||||
|
|
||||||
!!! note ""
|
!!! note ""
|
||||||
|
@ -86,6 +92,10 @@ Clash 中的默认模式,默认使用 `rule`。
|
||||||
|
|
||||||
将 `Selector` 中出站的选定的目标出站存储在缓存文件中。
|
将 `Selector` 中出站的选定的目标出站存储在缓存文件中。
|
||||||
|
|
||||||
|
#### store_fakeip
|
||||||
|
|
||||||
|
将 fakeip 存储在缓存文件中。
|
||||||
|
|
||||||
#### cache_file
|
#### cache_file
|
||||||
|
|
||||||
缓存文件路径,默认使用`cache.db`。
|
缓存文件路径,默认使用`cache.db`。
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"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)
|
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)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
|
||||||
"go.etcd.io/bbolt"
|
"go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
@ -15,6 +16,15 @@ import (
|
||||||
var (
|
var (
|
||||||
bucketSelected = []byte("selected")
|
bucketSelected = []byte("selected")
|
||||||
bucketExpand = []byte("group_expand")
|
bucketExpand = []byte("group_expand")
|
||||||
|
bucketMode = []byte("clash_mode")
|
||||||
|
|
||||||
|
bucketNameList = []string{
|
||||||
|
string(bucketSelected),
|
||||||
|
string(bucketExpand),
|
||||||
|
string(bucketMode),
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheIDDefault = []byte("default")
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.ClashCacheFile = (*CacheFile)(nil)
|
var _ adapter.ClashCacheFile = (*CacheFile)(nil)
|
||||||
|
@ -52,14 +62,14 @@ func Open(path string, cacheID string) (*CacheFile, error) {
|
||||||
if name[0] == 0 {
|
if name[0] == 0 {
|
||||||
return b.ForEachBucket(func(k []byte) error {
|
return b.ForEachBucket(func(k []byte) error {
|
||||||
bucketName := string(k)
|
bucketName := string(k)
|
||||||
if !(bucketName == string(bucketSelected) || bucketName == string(bucketExpand)) {
|
if !(common.Contains(bucketNameList, bucketName)) {
|
||||||
_ = b.DeleteBucket(name)
|
_ = b.DeleteBucket(name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
bucketName := string(name)
|
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)
|
_ = tx.DeleteBucket(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +88,39 @@ func Open(path string, cacheID string) (*CacheFile, error) {
|
||||||
}, nil
|
}, 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 {
|
func (c *CacheFile) bucket(t *bbolt.Tx, key []byte) *bbolt.Bucket {
|
||||||
if c.cacheID == nil {
|
if c.cacheID == nil {
|
||||||
return t.Bucket(key)
|
return t.Bucket(key)
|
||||||
|
|
|
@ -2,7 +2,6 @@ package clashapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
@ -10,11 +9,11 @@ import (
|
||||||
"github.com/go-chi/render"
|
"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 := chi.NewRouter()
|
||||||
r.Get("/", getConfigs(server, logFactory))
|
r.Get("/", getConfigs(server, logFactory))
|
||||||
r.Put("/", updateConfigs)
|
r.Put("/", updateConfigs)
|
||||||
r.Patch("/", patchConfigs(server, logger))
|
r.Patch("/", patchConfigs(server))
|
||||||
return r
|
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) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var newConfig configSchema
|
var newConfig configSchema
|
||||||
err := render.DecodeJSON(r.Body, &newConfig)
|
err := render.DecodeJSON(r.Body, &newConfig)
|
||||||
|
@ -58,11 +57,7 @@ func patchConfigs(server *Server, logger log.Logger) func(w http.ResponseWriter,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if newConfig.Mode != "" {
|
if newConfig.Mode != "" {
|
||||||
mode := strings.ToLower(newConfig.Mode)
|
server.SetMode(newConfig.Mode)
|
||||||
if server.mode != mode {
|
|
||||||
server.mode = mode
|
|
||||||
logger.Info("updated mode: ", mode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
render.NoContent(w, r)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,9 @@ type Server struct {
|
||||||
trafficManager *trafficontrol.Manager
|
trafficManager *trafficontrol.Manager
|
||||||
urlTestHistory *urltest.HistoryStorage
|
urlTestHistory *urltest.HistoryStorage
|
||||||
mode string
|
mode string
|
||||||
|
modeList []string
|
||||||
|
modeUpdateHook chan<- struct{}
|
||||||
|
storeMode bool
|
||||||
storeSelected bool
|
storeSelected bool
|
||||||
storeFakeIP bool
|
storeFakeIP bool
|
||||||
cacheFilePath string
|
cacheFilePath string
|
||||||
|
@ -70,9 +73,10 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
||||||
Handler: chiRouter,
|
Handler: chiRouter,
|
||||||
},
|
},
|
||||||
trafficManager: trafficManager,
|
trafficManager: trafficManager,
|
||||||
mode: strings.ToLower(options.DefaultMode),
|
modeList: options.ModeList,
|
||||||
storeSelected: options.StoreSelected,
|
|
||||||
externalController: options.ExternalController != "",
|
externalController: options.ExternalController != "",
|
||||||
|
storeMode: options.StoreMode,
|
||||||
|
storeSelected: options.StoreSelected,
|
||||||
storeFakeIP: options.StoreFakeIP,
|
storeFakeIP: options.StoreFakeIP,
|
||||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||||
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
||||||
|
@ -81,10 +85,15 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
||||||
if server.urlTestHistory == nil {
|
if server.urlTestHistory == nil {
|
||||||
server.urlTestHistory = urltest.NewHistoryStorage()
|
server.urlTestHistory = urltest.NewHistoryStorage()
|
||||||
}
|
}
|
||||||
if server.mode == "" {
|
defaultMode := "Rule"
|
||||||
server.mode = "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)
|
cachePath := os.ExpandEnv(options.CacheFile)
|
||||||
if cachePath == "" {
|
if cachePath == "" {
|
||||||
cachePath = "cache.db"
|
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("/logs", getLogs(logFactory))
|
||||||
r.Get("/traffic", traffic(trafficManager))
|
r.Get("/traffic", traffic(trafficManager))
|
||||||
r.Get("/version", version)
|
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("/proxies", proxyRouter(server, router))
|
||||||
r.Mount("/rules", ruleRouter(router))
|
r.Mount("/rules", ruleRouter(router))
|
||||||
r.Mount("/connections", connectionRouter(router, trafficManager))
|
r.Mount("/connections", connectionRouter(router, trafficManager))
|
||||||
|
@ -143,6 +152,14 @@ func (s *Server) PreStart() error {
|
||||||
return E.Cause(err, "open cache file")
|
return E.Cause(err, "open cache file")
|
||||||
}
|
}
|
||||||
s.cacheFile = cacheFile
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -170,6 +187,7 @@ func (s *Server) Close() error {
|
||||||
common.PtrOrNil(s.httpServer),
|
common.PtrOrNil(s.httpServer),
|
||||||
s.trafficManager,
|
s.trafficManager,
|
||||||
s.cacheFile,
|
s.cacheFile,
|
||||||
|
s.urlTestHistory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +195,43 @@ func (s *Server) Mode() string {
|
||||||
return s.mode
|
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 {
|
func (s *Server) StoreSelected() bool {
|
||||||
return s.storeSelected
|
return s.storeSelected
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,4 +9,6 @@ const (
|
||||||
CommandSelectOutbound
|
CommandSelectOutbound
|
||||||
CommandURLTest
|
CommandURLTest
|
||||||
CommandGroupExpand
|
CommandGroupExpand
|
||||||
|
CommandClashMode
|
||||||
|
CommandSetClashMode
|
||||||
)
|
)
|
||||||
|
|
135
experimental/libbox/command_clash_mode.go
Normal file
135
experimental/libbox/command_clash_mode.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package libbox
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
@ -26,6 +27,8 @@ type CommandClientHandler interface {
|
||||||
WriteLog(message string)
|
WriteLog(message string)
|
||||||
WriteStatus(message *StatusMessage)
|
WriteStatus(message *StatusMessage)
|
||||||
WriteGroups(message OutboundGroupIterator)
|
WriteGroups(message OutboundGroupIterator)
|
||||||
|
InitializeClashMode(modeList StringIterator, currentMode string)
|
||||||
|
UpdateClashMode(newMode string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStandaloneCommandClient() *CommandClient {
|
func NewStandaloneCommandClient() *CommandClient {
|
||||||
|
@ -79,6 +82,23 @@ func (c *CommandClient) Connect() error {
|
||||||
}
|
}
|
||||||
c.handler.Connected()
|
c.handler.Connected()
|
||||||
go c.handleGroupConn(conn)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,6 +199,9 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
||||||
}
|
}
|
||||||
group.items = append(group.items, &item)
|
group.items = append(group.items, &item)
|
||||||
}
|
}
|
||||||
|
if len(group.items) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
groups = append(groups, group)
|
groups = append(groups, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
|
@ -28,8 +29,8 @@ type CommandServer struct {
|
||||||
observer *observable.Observer[string]
|
observer *observable.Observer[string]
|
||||||
service *BoxService
|
service *BoxService
|
||||||
|
|
||||||
urlTestListener *list.Element[func()]
|
urlTestUpdate chan struct{}
|
||||||
urlTestUpdate chan struct{}
|
modeUpdate chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandServerHandler interface {
|
type CommandServerHandler interface {
|
||||||
|
@ -43,20 +44,18 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ
|
||||||
maxLines: int(maxLines),
|
maxLines: int(maxLines),
|
||||||
subscriber: observable.NewSubscriber[string](128),
|
subscriber: observable.NewSubscriber[string](128),
|
||||||
urlTestUpdate: make(chan struct{}, 1),
|
urlTestUpdate: make(chan struct{}, 1),
|
||||||
|
modeUpdate: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CommandServer) SetService(newService *BoxService) {
|
func (s *CommandServer) SetService(newService *BoxService) {
|
||||||
if s.service != nil && s.listener != nil {
|
if newService != nil {
|
||||||
service.PtrFromContext[urltest.HistoryStorage](s.service.ctx).RemoveListener(s.urlTestListener)
|
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
|
||||||
s.urlTestListener = nil
|
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
|
||||||
}
|
}
|
||||||
s.service = newService
|
s.service = newService
|
||||||
if newService != nil {
|
|
||||||
s.urlTestListener = service.PtrFromContext[urltest.HistoryStorage](newService.ctx).AddListener(s.notifyURLTestUpdate)
|
|
||||||
}
|
|
||||||
s.notifyURLTestUpdate()
|
s.notifyURLTestUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +155,10 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
||||||
return s.handleURLTest(conn)
|
return s.handleURLTest(conn)
|
||||||
case CommandGroupExpand:
|
case CommandGroupExpand:
|
||||||
return s.handleSetGroupExpand(conn)
|
return s.handleSetGroupExpand(conn)
|
||||||
|
case CommandClashMode:
|
||||||
|
return s.handleModeConn(conn)
|
||||||
|
case CommandSetClashMode:
|
||||||
|
return s.handleSetClashMode(conn)
|
||||||
default:
|
default:
|
||||||
return E.New("unknown command: ", command)
|
return E.New("unknown command: ", command)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,9 @@ func (p *platformLocalDNSTransport) Start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *platformLocalDNSTransport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Close() error {
|
func (p *platformLocalDNSTransport) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ type PlatformInterface interface {
|
||||||
UsePlatformInterfaceGetter() bool
|
UsePlatformInterfaceGetter() bool
|
||||||
GetInterfaces() (NetworkInterfaceIterator, error)
|
GetInterfaces() (NetworkInterfaceIterator, error)
|
||||||
UnderNetworkExtension() bool
|
UnderNetworkExtension() bool
|
||||||
|
ClearDNSCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunInterface interface {
|
type TunInterface interface {
|
||||||
|
|
|
@ -23,6 +23,7 @@ type Interface interface {
|
||||||
UsePlatformInterfaceGetter() bool
|
UsePlatformInterfaceGetter() bool
|
||||||
Interfaces() ([]NetworkInterface, error)
|
Interfaces() ([]NetworkInterface, error)
|
||||||
UnderNetworkExtension() bool
|
UnderNetworkExtension() bool
|
||||||
|
ClearDNSCache()
|
||||||
process.Searcher
|
process.Searcher
|
||||||
io.Writer
|
io.Writer
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type BoxService struct {
|
type BoxService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
instance *box.Box
|
instance *box.Box
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
|
urlTestHistoryStorage *urltest.HistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||||
|
@ -39,9 +40,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
||||||
runtimeDebug.FreeOSMemory()
|
runtimeDebug.FreeOSMemory()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||||
ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage())
|
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||||
sleepManager := pause.NewDefaultManager(ctx)
|
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||||
ctx = pause.ContextWithManager(ctx, sleepManager)
|
pauseManager := pause.NewDefaultManager(ctx)
|
||||||
|
ctx = pause.ContextWithManager(ctx, pauseManager)
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
@ -53,10 +55,11 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
||||||
}
|
}
|
||||||
runtimeDebug.FreeOSMemory()
|
runtimeDebug.FreeOSMemory()
|
||||||
return &BoxService{
|
return &BoxService{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
instance: instance,
|
instance: instance,
|
||||||
pauseManager: sleepManager,
|
urlTestHistoryStorage: urlTestHistoryStorage,
|
||||||
|
pauseManager: pauseManager,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +69,7 @@ func (s *BoxService) Start() error {
|
||||||
|
|
||||||
func (s *BoxService) Close() error {
|
func (s *BoxService) Close() error {
|
||||||
s.cancel()
|
s.cancel()
|
||||||
|
s.urlTestHistoryStorage.Close()
|
||||||
return s.instance.Close()
|
return s.instance.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,3 +198,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, er
|
||||||
func (w *platformInterfaceWrapper) UnderNetworkExtension() bool {
|
func (w *platformInterfaceWrapper) UnderNetworkExtension() bool {
|
||||||
return w.iif.UnderNetworkExtension()
|
return w.iif.UnderNetworkExtension()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) ClearDNSCache() {
|
||||||
|
w.iif.ClearDNSCache()
|
||||||
|
}
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -25,8 +25,8 @@ require (
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
|
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
|
||||||
github.com/sagernet/quic-go v0.0.0-20230824033040-30ef72e3be3e
|
github.com/sagernet/quic-go v0.0.0-20230824033040-30ef72e3be3e
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d
|
github.com/sagernet/sing v0.2.10-0.20230824115837-8d731e68853a
|
||||||
github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659
|
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-mux v0.1.3-0.20230811111955-dc1639b5204c
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.4
|
github.com/sagernet/sing-shadowsocks v0.2.4
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.3
|
github.com/sagernet/sing-shadowsocks2 v0.1.3
|
||||||
|
|
8
go.sum
8
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/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.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.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.20230824115837-8d731e68853a h1:eV4HEz9NP7eAlQ/IHD6OF2VVM6ke4Vw6htuSAsvgtDk=
|
||||||
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.20230824120133-4d5cbceb40c1 h1:5w+jXz8y/8UQAxO74TjftN5okYkpg5mGvVxXunlKdqI=
|
||||||
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 h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I=
|
||||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
|
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=
|
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
|
||||||
|
|
|
@ -7,10 +7,13 @@ type ClashAPIOptions struct {
|
||||||
ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"`
|
ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"`
|
||||||
Secret string `json:"secret,omitempty"`
|
Secret string `json:"secret,omitempty"`
|
||||||
DefaultMode string `json:"default_mode,omitempty"`
|
DefaultMode string `json:"default_mode,omitempty"`
|
||||||
|
StoreMode bool `json:"store_mode,omitempty"`
|
||||||
StoreSelected bool `json:"store_selected,omitempty"`
|
StoreSelected bool `json:"store_selected,omitempty"`
|
||||||
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
||||||
CacheFile string `json:"cache_file,omitempty"`
|
CacheFile string `json:"cache_file,omitempty"`
|
||||||
CacheID string `json:"cache_id,omitempty"`
|
CacheID string `json:"cache_id,omitempty"`
|
||||||
|
|
||||||
|
ModeList []string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectorOutboundOptions struct {
|
type SelectorOutboundOptions struct {
|
||||||
|
|
|
@ -1005,14 +1005,7 @@ func (r *Router) notifyNetworkUpdate(event int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conntrack.Close()
|
r.ResetNetwork()
|
||||||
|
|
||||||
for _, outbound := range r.outbounds {
|
|
||||||
listener, isListener := outbound.(adapter.InterfaceUpdateListener)
|
|
||||||
if isListener {
|
|
||||||
listener.InterfaceUpdated()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1025,5 +1018,9 @@ func (r *Router) ResetNetwork() error {
|
||||||
listener.InterfaceUpdated()
|
listener.InterfaceUpdated()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, transport := range r.transports {
|
||||||
|
transport.Reset()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,13 @@ func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr
|
||||||
return r.Lookup(ctx, domain, dns.DomainStrategyAsIS)
|
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) {
|
func LogDNSAnswers(logger log.ContextLogger, ctx context.Context, domain string, answers []mDNS.RR) {
|
||||||
for _, answer := range answers {
|
for _, answer := range answers {
|
||||||
logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String()))
|
logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String()))
|
||||||
|
|
|
@ -16,7 +16,7 @@ type ClashModeItem struct {
|
||||||
func NewClashModeItem(router adapter.Router, mode string) *ClashModeItem {
|
func NewClashModeItem(router adapter.Router, mode string) *ClashModeItem {
|
||||||
return &ClashModeItem{
|
return &ClashModeItem{
|
||||||
router: router,
|
router: router,
|
||||||
mode: strings.ToLower(mode),
|
mode: mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
if clashServer == nil {
|
if clashServer == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return clashServer.Mode() == r.mode
|
return strings.EqualFold(clashServer.Mode(), r.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClashModeItem) String() string {
|
func (r *ClashModeItem) String() string {
|
||||||
|
|
|
@ -10,7 +10,7 @@ require (
|
||||||
github.com/docker/docker v24.0.5+incompatible
|
github.com/docker/docker v24.0.5+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/gofrs/uuid/v5 v5.0.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-shadowsocks v0.2.4
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.3
|
github.com/sagernet/sing-shadowsocks2 v0.1.3
|
||||||
github.com/spyzhov/ajson v0.9.0
|
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/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||||
github.com/sagernet/quic-go v0.0.0-20230824033040-30ef72e3be3e // 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/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-mux v0.1.3-0.20230811111955-dc1639b5204c // indirect
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
|
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
|
||||||
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 // indirect
|
github.com/sagernet/sing-tun v0.1.12-0.20230821065522-7545dc2d5641 // indirect
|
||||||
|
|
|
@ -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.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 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.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 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.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 h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I=
|
||||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
|
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=
|
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
|
||||||
|
|
|
@ -85,6 +85,9 @@ func (t *Transport) Start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
if t.interfaceCallback != nil {
|
if t.interfaceCallback != nil {
|
||||||
t.router.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
t.router.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
||||||
|
|
|
@ -54,6 +54,9 @@ func (s *Transport) Start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Transport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Transport) Close() error {
|
func (s *Transport) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user