Add clash mode support

This commit is contained in:
世界 2022-09-10 14:09:47 +08:00
parent 80cfc9a25b
commit 5297273937
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
12 changed files with 98 additions and 23 deletions

View file

@ -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

View file

@ -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 {

2
box.go
View file

@ -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,

View file

@ -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) {
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) {

View file

@ -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 != "" {

View file

@ -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"`

View file

@ -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"`

View file

@ -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"`
}

View file

@ -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 {

View file

@ -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
route/rule_clash_mode.go Normal file
View file

@ -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
}

View file

@ -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
}