瀏覽代碼

Add clash mode support

世界 3 年之前
父節點
當前提交
5297273937
共有 12 個文件被更改,包括 98 次插入23 次删除
  1. 3 6
      adapter/experimental.go
  2. 3 1
      adapter/router.go
  3. 1 1
      box.go
  4. 24 7
      experimental/clashapi/configs.go
  5. 10 1
      experimental/clashapi/server.go
  6. 1 0
      option/clash.go
  7. 1 0
      option/dns.go
  8. 1 0
      option/route.go
  9. 11 7
      route/router.go
  10. 5 0
      route/rule.go
  11. 33 0
      route/rule_clash_mode.go
  12. 5 0
      route/rule_dns.go

+ 3 - 6
adapter/experimental.go

@@ -9,18 +9,15 @@ import (
 
 type ClashServer interface {
 	Service
-	TrafficController
+	Mode() string
+	RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
+	RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
 }
 
 type Tracker interface {
 	Leave()
 }
 
-type TrafficController interface {
-	RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
-	RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
-}
-
 type OutboundGroup interface {
 	Now() string
 	All() []string

+ 3 - 1
adapter/router.go

@@ -39,7 +39,9 @@ type Router interface {
 	InterfaceMonitor() tun.DefaultInterfaceMonitor
 	PackageManager() tun.PackageManager
 	Rules() []Rule
-	SetTrafficController(controller TrafficController)
+
+	ClashServer() ClashServer
+	SetClashServer(controller ClashServer)
 }
 
 type Rule interface {

+ 1 - 1
box.go

@@ -154,7 +154,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
 		if err != nil {
 			return nil, E.Cause(err, "create clash api server")
 		}
-		router.SetTrafficController(clashServer)
+		router.SetClashServer(clashServer)
 	}
 	return &Box{
 		router:      router,

+ 24 - 7
experimental/clashapi/configs.go

@@ -2,6 +2,7 @@ package clashapi
 
 import (
 	"net/http"
+	"strings"
 
 	"github.com/sagernet/sing-box/log"
 
@@ -9,11 +10,11 @@ import (
 	"github.com/go-chi/render"
 )
 
-func configRouter(logFactory log.Factory) http.Handler {
+func configRouter(server *Server, logFactory log.Factory, logger log.Logger) http.Handler {
 	r := chi.NewRouter()
-	r.Get("/", getConfigs(logFactory))
+	r.Get("/", getConfigs(server, logFactory))
 	r.Put("/", updateConfigs)
-	r.Patch("/", patchConfigs)
+	r.Patch("/", patchConfigs(server, logger))
 	return r
 }
 
@@ -31,7 +32,7 @@ type configSchema struct {
 	Tun         map[string]any `json:"tun"`
 }
 
-func getConfigs(logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) {
+func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWriter, r *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		logLevel := logFactory.Level()
 		if logLevel == log.LevelTrace {
@@ -40,15 +41,31 @@ func getConfigs(logFactory log.Factory) func(w http.ResponseWriter, r *http.Requ
 			logLevel = log.LevelError
 		}
 		render.JSON(w, r, &configSchema{
-			Mode:        "rule",
+			Mode:        server.mode,
 			BindAddress: "*",
 			LogLevel:    log.FormatLevel(logLevel),
 		})
 	}
 }
 
-func patchConfigs(w http.ResponseWriter, r *http.Request) {
-	render.NoContent(w, r)
+func patchConfigs(server *Server, logger log.Logger) func(w http.ResponseWriter, r *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var newConfig configSchema
+		err := render.DecodeJSON(r.Body, &newConfig)
+		if err != nil {
+			render.Status(r, http.StatusBadRequest)
+			render.JSON(w, r, ErrBadRequest)
+			return
+		}
+		if newConfig.Mode != "" {
+			mode := strings.ToLower(newConfig.Mode)
+			if server.mode != mode {
+				server.mode = mode
+				logger.Info("updated mode: ", mode)
+			}
+		}
+		render.NoContent(w, r)
+	}
 }
 
 func updateConfigs(w http.ResponseWriter, r *http.Request) {

+ 10 - 1
experimental/clashapi/server.go

@@ -37,6 +37,7 @@ type Server struct {
 	trafficManager *trafficontrol.Manager
 	urlTestHistory *urltest.HistoryStorage
 	tcpListener    net.Listener
+	mode           string
 }
 
 func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) *Server {
@@ -51,6 +52,10 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
 		},
 		trafficManager: trafficManager,
 		urlTestHistory: urltest.NewHistoryStorage(),
+		mode:           strings.ToLower(options.DefaultMode),
+	}
+	if server.mode == "" {
+		server.mode = "rule"
 	}
 	cors := cors.New(cors.Options{
 		AllowedOrigins: []string{"*"},
@@ -65,7 +70,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
 		r.Get("/logs", getLogs(logFactory))
 		r.Get("/traffic", traffic(trafficManager))
 		r.Get("/version", version)
-		r.Mount("/configs", configRouter(logFactory))
+		r.Mount("/configs", configRouter(server, logFactory, server.logger))
 		r.Mount("/proxies", proxyRouter(server, router))
 		r.Mount("/rules", ruleRouter(router))
 		r.Mount("/connections", connectionRouter(trafficManager))
@@ -121,6 +126,10 @@ func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn,
 	return tracker, tracker
 }
 
+func (s *Server) Mode() string {
+	return s.mode
+}
+
 func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata {
 	var inbound string
 	if metadata.Inbound != "" {

+ 1 - 0
option/clash.go

@@ -1,6 +1,7 @@
 package option
 
 type ClashAPIOptions struct {
+	DefaultMode        string `json:"default_mode,omitempty"`
 	ExternalController string `json:"external_controller,omitempty"`
 	ExternalUI         string `json:"external_ui,omitempty"`
 	Secret             string `json:"secret,omitempty"`

+ 1 - 0
option/dns.go

@@ -99,6 +99,7 @@ type DefaultDNSRule struct {
 	User            Listable[string] `json:"user,omitempty"`
 	UserID          Listable[int32]  `json:"user_id,omitempty"`
 	Outbound        Listable[string] `json:"outbound,omitempty"`
+	ClashMode       string           `json:"clash_mode,omitempty"`
 	Invert          bool             `json:"invert,omitempty"`
 	Server          string           `json:"server,omitempty"`
 	DisableCache    bool             `json:"disable_cache,omitempty"`

+ 1 - 0
option/route.go

@@ -101,6 +101,7 @@ type DefaultRule struct {
 	PackageName     Listable[string] `json:"package_name,omitempty"`
 	User            Listable[string] `json:"user,omitempty"`
 	UserID          Listable[int32]  `json:"user_id,omitempty"`
+	ClashMode       string           `json:"clash_mode,omitempty"`
 	Invert          bool             `json:"invert,omitempty"`
 	Outbound        string           `json:"outbound,omitempty"`
 }

+ 11 - 7
route/router.go

@@ -93,7 +93,7 @@ type Router struct {
 	networkMonitor                     tun.NetworkUpdateMonitor
 	interfaceMonitor                   tun.DefaultInterfaceMonitor
 	packageManager                     tun.PackageManager
-	trafficController                  adapter.TrafficController
+	clashServer                        adapter.ClashServer
 	processSearcher                    process.Searcher
 }
 
@@ -588,8 +588,8 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
 		conn.Close()
 		return E.New("missing supported outbound, closing connection")
 	}
-	if r.trafficController != nil {
-		trackerConn, tracker := r.trafficController.RoutedConnection(ctx, conn, metadata, matchedRule)
+	if r.clashServer != nil {
+		trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule)
 		defer tracker.Leave()
 		conn = trackerConn
 	}
@@ -661,8 +661,8 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
 		conn.Close()
 		return E.New("missing supported outbound, closing packet connection")
 	}
-	if r.trafficController != nil {
-		trackerConn, tracker := r.trafficController.RoutedPacketConnection(ctx, conn, metadata, matchedRule)
+	if r.clashServer != nil {
+		trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, matchedRule)
 		defer tracker.Leave()
 		conn = trackerConn
 	}
@@ -746,8 +746,12 @@ func (r *Router) PackageManager() tun.PackageManager {
 	return r.packageManager
 }
 
-func (r *Router) SetTrafficController(controller adapter.TrafficController) {
-	r.trafficController = controller
+func (r *Router) ClashServer() adapter.ClashServer {
+	return r.clashServer
+}
+
+func (r *Router) SetClashServer(controller adapter.ClashServer) {
+	r.clashServer = controller
 }
 
 func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {

+ 5 - 0
route/rule.go

@@ -192,6 +192,11 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
 		rule.items = append(rule.items, item)
 		rule.allItems = append(rule.allItems, item)
 	}
+	if options.ClashMode != "" {
+		item := NewClashModeItem(router, options.ClashMode)
+		rule.items = append(rule.items, item)
+		rule.allItems = append(rule.allItems, item)
+	}
 	return rule, nil
 }
 

+ 33 - 0
route/rule_clash_mode.go

@@ -0,0 +1,33 @@
+package route
+
+import (
+	"strings"
+
+	"github.com/sagernet/sing-box/adapter"
+)
+
+var _ RuleItem = (*ClashModeItem)(nil)
+
+type ClashModeItem struct {
+	router adapter.Router
+	mode   string
+}
+
+func NewClashModeItem(router adapter.Router, mode string) *ClashModeItem {
+	return &ClashModeItem{
+		router: router,
+		mode:   strings.ToLower(mode),
+	}
+}
+
+func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool {
+	clashServer := r.router.ClashServer()
+	if clashServer == nil {
+		return false
+	}
+	return clashServer.Mode() == r.mode
+}
+
+func (r *ClashModeItem) String() string {
+	return "clash_mode=" + r.mode
+}

+ 5 - 0
route/rule_dns.go

@@ -180,6 +180,11 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
 		rule.items = append(rule.items, item)
 		rule.allItems = append(rule.allItems, item)
 	}
+	if options.ClashMode != "" {
+		item := NewClashModeItem(router, options.ClashMode)
+		rule.items = append(rule.items, item)
+		rule.allItems = append(rule.allItems, item)
+	}
 	return rule, nil
 }