From 5297273937ea2e050915f2896f63e07f6ad80bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 10 Sep 2022 14:09:47 +0800 Subject: [PATCH] Add clash mode support --- adapter/experimental.go | 9 +++------ adapter/router.go | 4 +++- box.go | 2 +- experimental/clashapi/configs.go | 31 +++++++++++++++++++++++------- experimental/clashapi/server.go | 11 ++++++++++- option/clash.go | 1 + option/dns.go | 1 + option/route.go | 1 + route/router.go | 18 ++++++++++------- route/rule.go | 5 +++++ route/rule_clash_mode.go | 33 ++++++++++++++++++++++++++++++++ route/rule_dns.go | 5 +++++ 12 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 route/rule_clash_mode.go diff --git a/adapter/experimental.go b/adapter/experimental.go index bdbf941a..66438986 100644 --- a/adapter/experimental.go +++ b/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 diff --git a/adapter/router.go b/adapter/router.go index 4e32513d..b26a62d2 100644 --- a/adapter/router.go +++ b/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 { diff --git a/box.go b/box.go index 53607ede..fe39b679 100644 --- a/box.go +++ b/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, diff --git a/experimental/clashapi/configs.go b/experimental/clashapi/configs.go index 334caf4c..bdb1aa1c 100644 --- a/experimental/clashapi/configs.go +++ b/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) { diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 2d473931..ac7cfa0b 100644 --- a/experimental/clashapi/server.go +++ b/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 != "" { diff --git a/option/clash.go b/option/clash.go index eb8b0ef6..2c059fe9 100644 --- a/option/clash.go +++ b/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"` diff --git a/option/dns.go b/option/dns.go index d8da0ff1..b232baea 100644 --- a/option/dns.go +++ b/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"` diff --git a/option/route.go b/option/route.go index 74f67d02..308c4802 100644 --- a/option/route.go +++ b/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"` } diff --git a/route/router.go b/route/router.go index 9b8a076b..012f535f 100644 --- a/route/router.go +++ b/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 { diff --git a/route/rule.go b/route/rule.go index 12186e4b..0d549197 100644 --- a/route/rule.go +++ b/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 } diff --git a/route/rule_clash_mode.go b/route/rule_clash_mode.go new file mode 100644 index 00000000..888577e4 --- /dev/null +++ b/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 +} diff --git a/route/rule_dns.go b/route/rule_dns.go index 6364f548..cbe15335 100644 --- a/route/rule_dns.go +++ b/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 }