Skip to content

Commit

Permalink
Add store_mode and platform Clash mode selector
Browse files Browse the repository at this point in the history
  • Loading branch information
nekohasekai committed Aug 24, 2023
1 parent 6dcacf3 commit ccc5486
Show file tree
Hide file tree
Showing 26 changed files with 371 additions and 71 deletions.
3 changes: 3 additions & 0 deletions adapter/experimental.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type ClashServer interface {
Service
PreStarter
Mode() string
ModeList() []string
StoreSelected() bool
StoreFakeIP() bool
CacheFile() ClashCacheFile
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions adapter/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion box.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
30 changes: 14 additions & 16 deletions common/urltest/urltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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"
Expand Down
26 changes: 26 additions & 0 deletions experimental/clashapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
47 changes: 45 additions & 2 deletions experimental/clashapi/cachefile/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@ import (
"time"

"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"

"go.etcd.io/bbolt"
)

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)
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -100,6 +110,39 @@ func (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error)
return bucket.CreateBucketIfNotExists(key)
}

func (c *CacheFile) LoadMode() string {
var mode string
c.DB.View(func(t *bbolt.Tx) error {
bucket := c.bucket(t, 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) LoadSelected(group string) string {
var selected string
c.DB.View(func(t *bbolt.Tx) error {
Expand Down
13 changes: 4 additions & 9 deletions experimental/clashapi/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ package clashapi

import (
"net/http"
"strings"

"github.com/sagernet/sing-box/log"

"github.com/go-chi/chi/v5"
"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
}

Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down
56 changes: 51 additions & 5 deletions experimental/clashapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -70,7 +73,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
Handler: chiRouter,
},
trafficManager: trafficManager,
mode: strings.ToLower(options.DefaultMode),
modeList: options.ModeList,
storeSelected: options.StoreSelected,
externalController: options.ExternalController != "",
storeFakeIP: options.StoreFakeIP,
Expand All @@ -81,10 +84,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"
Expand All @@ -110,7 +118,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))
Expand Down Expand Up @@ -143,6 +151,12 @@ func (s *Server) PreStart() error {
return E.Cause(err, "open cache file")
}
s.cacheFile = cacheFile
if s.storeMode {
mode := s.cacheFile.LoadMode()
if common.Contains(s.modeList, mode) {
s.mode = mode
}
}
}
return nil
}
Expand Down Expand Up @@ -170,13 +184,45 @@ func (s *Server) Close() error {
common.PtrOrNil(s.httpServer),
s.trafficManager,
s.cacheFile,
s.urlTestHistory,
)
}

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()
s.logger.Info("updated mode: ", newMode)
}

func (s *Server) StoreSelected() bool {
return s.storeSelected
}
Expand Down
2 changes: 2 additions & 0 deletions experimental/libbox/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ const (
CommandSelectOutbound
CommandURLTest
CommandGroupExpand
CommandClashMode
CommandSetClashMode
)
Loading

0 comments on commit ccc5486

Please sign in to comment.