Add RuleSet ClashAPI (Rule Provider)

This commit is contained in:
0xffffharry 2023-12-20 13:05:49 +08:00
parent bdfe6ec626
commit ac283332e3
6 changed files with 101 additions and 31 deletions

View File

@ -4,10 +4,11 @@ import (
"context" "context"
"net/http" "net/http"
"net/netip" "net/netip"
"time"
"github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-dns" dns "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun" tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
@ -32,6 +33,7 @@ type Router interface {
LoadGeosite(code string) (Rule, error) LoadGeosite(code string) (Rule, error)
RuleSet(tag string) (RuleSet, bool) RuleSet(tag string) (RuleSet, bool)
RuleSets() []RuleSet
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)
@ -87,6 +89,8 @@ type DNSRule interface {
} }
type RuleSet interface { type RuleSet interface {
Tag() string
Type() string
StartContext(ctx context.Context, startContext RuleSetStartContext) error StartContext(ctx context.Context, startContext RuleSetStartContext) error
PostStart() error PostStart() error
Metadata() RuleSetMetadata Metadata() RuleSetMetadata
@ -97,6 +101,9 @@ type RuleSet interface {
type RuleSetMetadata struct { type RuleSetMetadata struct {
ContainsProcessRule bool ContainsProcessRule bool
ContainsWIFIRule bool ContainsWIFIRule bool
RuleNum int
LastUpdated time.Time
Format string
} }
type RuleSetStartContext interface { type RuleSetStartContext interface {

View File

@ -1,34 +1,49 @@
package clashapi package clashapi
import ( import (
"context"
"net/http" "net/http"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/json/badjson"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
) )
func ruleProviderRouter() http.Handler { func ruleProviderRouter(router adapter.Router) http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Get("/", getRuleProviders) r.Get("/", getRuleProviders(router))
r.Route("/{name}", func(r chi.Router) { r.Route("/{name}", func(r chi.Router) {
r.Use(parseProviderName, findRuleProviderByName) r.Use(parseProviderName, findRuleProviderByName(router))
r.Get("/", getRuleProvider) r.Get("/", getRuleProvider)
r.Put("/", updateRuleProvider) r.Put("/", updateRuleProvider)
}) })
return r return r
} }
func getRuleProviders(w http.ResponseWriter, r *http.Request) { func getRuleProviders(router adapter.Router) http.HandlerFunc {
render.JSON(w, r, render.M{ return func(w http.ResponseWriter, r *http.Request) {
"providers": []string{}, ruleSets := router.RuleSets()
}) if len(ruleSets) == 0 {
render.JSON(w, r, render.M{
"providers": []string{},
})
}
m := render.M{}
for _, ruleSet := range ruleSets {
m[ruleSet.Tag()] = ruleProviderInfo(ruleSet)
}
render.JSON(w, r, render.M{
"providers": m,
})
}
} }
func getRuleProvider(w http.ResponseWriter, r *http.Request) { func getRuleProvider(w http.ResponseWriter, r *http.Request) {
// provider := r.Context().Value(CtxKeyProvider).(provider.RuleProvider) ruleSet := r.Context().Value(CtxKeyProvider).(adapter.RuleSet)
// render.JSON(w, r, provider) render.JSON(w, r, ruleProviderInfo(ruleSet))
render.NoContent(w, r)
} }
func updateRuleProvider(w http.ResponseWriter, r *http.Request) { func updateRuleProvider(w http.ResponseWriter, r *http.Request) {
@ -41,18 +56,36 @@ func updateRuleProvider(w http.ResponseWriter, r *http.Request) {
render.NoContent(w, r) render.NoContent(w, r)
} }
func findRuleProviderByName(next http.Handler) http.Handler { func findRuleProviderByName(router adapter.Router) func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return func(next http.Handler) http.Handler {
/*name := r.Context().Value(CtxKeyProviderName).(string) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
providers := tunnel.RuleProviders() name := r.Context().Value(CtxKeyProviderName).(string)
provider, exist := providers[name] ruleSet, exist := router.RuleSet(name)
if !exist {*/ if !exist {
render.Status(r, http.StatusNotFound) render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound) render.JSON(w, r, ErrNotFound)
//return return
//} }
// ctx := context.WithValue(r.Context(), CtxKeyProvider, provider) ctx := context.WithValue(r.Context(), CtxKeyProvider, ruleSet)
// next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
}
}
func ruleProviderInfo(ruleSet adapter.RuleSet) *badjson.JSONObject {
var info badjson.JSONObject
info.Put("name", ruleSet.Tag())
info.Put("type", "Rule")
if ruleSet.Type() == "remote" {
info.Put("vehicleType", "HTTP")
} else {
info.Put("vehicleType", "File")
}
metadata := ruleSet.Metadata()
info.Put("format", metadata.Format)
info.Put("behavior", "sing")
info.Put("ruleCount", metadata.RuleNum)
info.Put("updatedAt", metadata.LastUpdated)
return &info
} }

View File

@ -107,7 +107,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
r.Mount("/rules", ruleRouter(router)) r.Mount("/rules", ruleRouter(router))
r.Mount("/connections", connectionRouter(router, trafficManager)) r.Mount("/connections", connectionRouter(router, trafficManager))
r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/proxies", proxyProviderRouter())
r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter(router))
r.Mount("/script", scriptRouter()) r.Mount("/script", scriptRouter())
r.Mount("/profile", profileRouter()) r.Mount("/profile", profileRouter())
r.Mount("/cache", cacheRouter(ctx)) r.Mount("/cache", cacheRouter(ctx))

View File

@ -26,10 +26,10 @@ import (
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/transport/fakeip" "github.com/sagernet/sing-box/transport/fakeip"
"github.com/sagernet/sing-dns" dns "github.com/sagernet/sing-dns"
mux "github.com/sagernet/sing-mux" mux "github.com/sagernet/sing-mux"
"github.com/sagernet/sing-tun" tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-vmess" vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
@ -713,6 +713,10 @@ func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) {
return ruleSet, loaded return ruleSet, loaded
} }
func (r *Router) RuleSets() []adapter.RuleSet {
return r.ruleSets
}
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if r.pauseManager.IsDevicePaused() { if r.pauseManager.IsDevicePaused() {
return E.New("reject connection to ", metadata.Destination, " while device paused") return E.New("reject connection to ", metadata.Destination, " while device paused")

View File

@ -3,6 +3,7 @@ package route
import ( import (
"context" "context"
"os" "os"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/srs" "github.com/sagernet/sing-box/common/srs"
@ -15,6 +16,7 @@ import (
var _ adapter.RuleSet = (*LocalRuleSet)(nil) var _ adapter.RuleSet = (*LocalRuleSet)(nil)
type LocalRuleSet struct { type LocalRuleSet struct {
tag string
rules []adapter.HeadlessRule rules []adapter.HeadlessRule
metadata adapter.RuleSetMetadata metadata adapter.RuleSetMetadata
} }
@ -53,7 +55,18 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS
var metadata adapter.RuleSetMetadata var metadata adapter.RuleSetMetadata
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
return &LocalRuleSet{rules, metadata}, nil metadata.RuleNum = len(rules)
metadata.LastUpdated = time.Now()
metadata.Format = options.Format
return &LocalRuleSet{options.Tag, rules, metadata}, nil
}
func (s *LocalRuleSet) Tag() string {
return s.tag
}
func (s *LocalRuleSet) Type() string {
return "local"
} }
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool { func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {

View File

@ -59,6 +59,14 @@ func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.
} }
} }
func (s *RemoteRuleSet) Tag() string {
return s.options.Tag
}
func (s *RemoteRuleSet) Type() string {
return "remote"
}
func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool { func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
for _, rule := range s.rules { for _, rule := range s.rules {
if rule.Match(metadata) { if rule.Match(metadata) {
@ -117,7 +125,10 @@ func (s *RemoteRuleSet) PostStart() error {
} }
func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata { func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
return s.metadata metadata := s.metadata
metadata.LastUpdated = s.lastUpdated
metadata.Format = s.options.Format
return metadata
} }
func (s *RemoteRuleSet) loadBytes(content []byte) error { func (s *RemoteRuleSet) loadBytes(content []byte) error {
@ -152,6 +163,8 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
} }
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
s.metadata.RuleNum = len(rules)
s.lastUpdated = time.Now()
s.rules = rules s.rules = rules
return nil return nil
} }