mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Add RuleSet ClashAPI (Rule Provider)
This commit is contained in:
parent
bdfe6ec626
commit
ac283332e3
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ruleSets := router.RuleSets()
|
||||||
|
if len(ruleSets) == 0 {
|
||||||
render.JSON(w, r, render.M{
|
render.JSON(w, r, render.M{
|
||||||
"providers": []string{},
|
"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 func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
/*name := r.Context().Value(CtxKeyProviderName).(string)
|
name := r.Context().Value(CtxKeyProviderName).(string)
|
||||||
providers := tunnel.RuleProviders()
|
ruleSet, exist := router.RuleSet(name)
|
||||||
provider, exist := providers[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
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user