mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
add rule-provider clash-api
(cherry picked from commit e6e574a8e868a630fe17ce6cab05a68fe96653c1)
This commit is contained in:
parent
0a59285020
commit
0ec07e573e
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/geoip"
|
||||
"github.com/sagernet/sing-dns"
|
||||
@ -34,6 +35,7 @@ type Router interface {
|
||||
GeoIPReader() *geoip.Reader
|
||||
LoadGeosite(code string) (Rule, error)
|
||||
|
||||
RuleSets() []RuleSet
|
||||
RuleSet(tag string) (RuleSet, bool)
|
||||
|
||||
NeedWIFIState() bool
|
||||
@ -76,6 +78,7 @@ func RouterFromContext(ctx context.Context) Router {
|
||||
|
||||
type HeadlessRule interface {
|
||||
Match(metadata *InboundContext) bool
|
||||
RuleCount() uint64
|
||||
String() string
|
||||
}
|
||||
|
||||
@ -98,6 +101,10 @@ type DNSRule interface {
|
||||
|
||||
type RuleSet interface {
|
||||
Name() string
|
||||
Type() string
|
||||
Format() string
|
||||
UpdatedTime() time.Time
|
||||
Update(ctx context.Context) error
|
||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||
PostStart() error
|
||||
Metadata() RuleSetMetadata
|
||||
|
@ -1,58 +1,93 @@
|
||||
package clashapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func ruleProviderRouter() http.Handler {
|
||||
func ruleProviderRouter(router adapter.Router) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getRuleProviders)
|
||||
r.Get("/", getRuleProviders(router))
|
||||
|
||||
r.Route("/{name}", func(r chi.Router) {
|
||||
r.Use(parseProviderName, findRuleProviderByName)
|
||||
r.Use(parseProviderName, findRuleProviderByName(router))
|
||||
r.Get("/", getRuleProvider)
|
||||
r.Put("/", updateRuleProvider)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func getRuleProviders(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, render.M{
|
||||
"providers": []string{},
|
||||
})
|
||||
func ruleSetInfo(ruleSet adapter.RuleSet) *badjson.JSONObject {
|
||||
var info badjson.JSONObject
|
||||
info.Put("name", ruleSet.Name())
|
||||
info.Put("type", "Rule")
|
||||
info.Put("vehicleType", strings.ToUpper(ruleSet.Type()))
|
||||
info.Put("behavior", strings.ToUpper(ruleSet.Format()))
|
||||
info.Put("ruleCount", ruleSet.RuleCount())
|
||||
info.Put("updatedAt", ruleSet.UpdatedTime().Format("2006-01-02T15:04:05.999999999-07:00"))
|
||||
return &info
|
||||
}
|
||||
|
||||
func getRuleProviders(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
providerMap := render.M{}
|
||||
for i, ruleSet := range router.RuleSets() {
|
||||
var tag string
|
||||
if ruleSet.Name() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = ruleSet.Name()
|
||||
}
|
||||
providerMap[tag] = ruleSetInfo(ruleSet)
|
||||
}
|
||||
render.JSON(w, r, render.M{
|
||||
"providers": providerMap,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getRuleProvider(w http.ResponseWriter, r *http.Request) {
|
||||
// provider := r.Context().Value(CtxKeyProvider).(provider.RuleProvider)
|
||||
// render.JSON(w, r, provider)
|
||||
render.NoContent(w, r)
|
||||
ruleSet := r.Context().Value(CtxKeyProvider).(adapter.RuleSet)
|
||||
response, err := ruleSetInfo(ruleSet).MarshalJSON()
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
w.Write(response)
|
||||
}
|
||||
|
||||
func updateRuleProvider(w http.ResponseWriter, r *http.Request) {
|
||||
/*provider := r.Context().Value(CtxKeyProvider).(provider.RuleProvider)
|
||||
if err := provider.Update(); err != nil {
|
||||
render.Status(r, http.StatusServiceUnavailable)
|
||||
ruleSet := r.Context().Value(CtxKeyProvider).(adapter.RuleSet)
|
||||
err := ruleSet.Update(r.Context())
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}*/
|
||||
}
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
func findRuleProviderByName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
/*name := r.Context().Value(CtxKeyProviderName).(string)
|
||||
providers := tunnel.RuleProviders()
|
||||
provider, exist := providers[name]
|
||||
if !exist {*/
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
//return
|
||||
//}
|
||||
|
||||
// ctx := context.WithValue(r.Context(), CtxKeyProvider, provider)
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
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) {
|
||||
name := r.Context().Value(CtxKeyProviderName).(string)
|
||||
provider, exist := router.RuleSet(name)
|
||||
if !exist {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), CtxKeyProvider, provider)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
||||
r.Mount("/rules", ruleRouter(router))
|
||||
r.Mount("/connections", connectionRouter(router, trafficManager))
|
||||
r.Mount("/providers/proxies", proxyProviderRouter())
|
||||
r.Mount("/providers/rules", ruleProviderRouter())
|
||||
r.Mount("/providers/rules", ruleProviderRouter(router))
|
||||
r.Mount("/script", scriptRouter())
|
||||
r.Mount("/profile", profileRouter())
|
||||
r.Mount("/cache", cacheRouter(ctx))
|
||||
|
@ -57,7 +57,7 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
|
||||
return E.New("unknown rule-set format: " + r.Format)
|
||||
}
|
||||
} else {
|
||||
r.Format = ""
|
||||
r.Format = C.RuleSetFormatSource
|
||||
r.Path = ""
|
||||
}
|
||||
var v any
|
||||
|
@ -784,6 +784,10 @@ func (r *Router) FakeIPStore() adapter.FakeIPStore {
|
||||
return r.fakeIPStore
|
||||
}
|
||||
|
||||
func (r *Router) RuleSets() []adapter.RuleSet {
|
||||
return r.ruleSets
|
||||
}
|
||||
|
||||
func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) {
|
||||
ruleSet, loaded := r.ruleSetMap[tag]
|
||||
return ruleSet, loaded
|
||||
|
@ -19,6 +19,7 @@ type abstractDefaultRule struct {
|
||||
destinationPortItems []RuleItem
|
||||
allItems []RuleItem
|
||||
ruleSetItem RuleItem
|
||||
ruleCount uint64
|
||||
invert bool
|
||||
outbound string
|
||||
}
|
||||
@ -27,6 +28,10 @@ func (r *abstractDefaultRule) Type() string {
|
||||
return C.RuleTypeDefault
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) RuleCount() uint64 {
|
||||
return r.ruleCount
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) Start() error {
|
||||
for _, item := range r.allItems {
|
||||
if starter, isStarter := item.(interface {
|
||||
@ -163,16 +168,21 @@ func (r *abstractDefaultRule) String() string {
|
||||
}
|
||||
|
||||
type abstractLogicalRule struct {
|
||||
rules []adapter.HeadlessRule
|
||||
mode string
|
||||
invert bool
|
||||
outbound string
|
||||
rules []adapter.HeadlessRule
|
||||
mode string
|
||||
invert bool
|
||||
outbound string
|
||||
ruleCount uint64
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) Type() string {
|
||||
return C.RuleTypeLogical
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) RuleCount() uint64 {
|
||||
return r.ruleCount
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) UpdateGeosite() error {
|
||||
for _, rule := range common.FilterIsInstance(r.rules, func(it adapter.HeadlessRule) (adapter.Rule, bool) {
|
||||
rule, loaded := it.(adapter.Rule)
|
||||
|
@ -159,6 +159,18 @@ func NewDefaultHeadlessRule(router adapter.Router, options option.DefaultHeadles
|
||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
switch true {
|
||||
case len(rule.allItems) == len(rule.destinationAddressItems)+len(rule.destinationIPCIDRItems):
|
||||
rule.ruleCount = uint64(len(rule.destinationAddressItems) + len(rule.destinationIPCIDRItems))
|
||||
case len(rule.allItems) == len(rule.sourceAddressItems):
|
||||
rule.ruleCount = uint64(len(rule.sourceAddressItems))
|
||||
case len(rule.allItems) == len(rule.sourcePortItems):
|
||||
rule.ruleCount = uint64(len(rule.sourcePortItems))
|
||||
case len(rule.allItems) == len(rule.destinationPortItems):
|
||||
rule.ruleCount = uint64(len(rule.destinationPortItems))
|
||||
default:
|
||||
rule.ruleCount = 1
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
@ -190,5 +202,6 @@ func NewLogicalHeadlessRule(router adapter.Router, options option.LogicalHeadles
|
||||
}
|
||||
r.rules[i] = rule
|
||||
}
|
||||
r.ruleCount = 1
|
||||
return r, nil
|
||||
}
|
||||
|
@ -26,9 +26,11 @@ type abstractRuleSet struct {
|
||||
router adapter.Router
|
||||
logger logger.ContextLogger
|
||||
tag string
|
||||
sType string
|
||||
path string
|
||||
format string
|
||||
rules []adapter.HeadlessRule
|
||||
ruleCount uint64
|
||||
metadata adapter.RuleSetMetadata
|
||||
lastUpdated time.Time
|
||||
refs atomic.Int32
|
||||
@ -38,6 +40,22 @@ func (s *abstractRuleSet) Name() string {
|
||||
return s.tag
|
||||
}
|
||||
|
||||
func (s *abstractRuleSet) Type() string {
|
||||
return s.sType
|
||||
}
|
||||
|
||||
func (s *abstractRuleSet) Format() string {
|
||||
return s.format
|
||||
}
|
||||
|
||||
func (s *abstractRuleSet) RuleCount() uint64 {
|
||||
return s.ruleCount
|
||||
}
|
||||
|
||||
func (s *abstractRuleSet) UpdatedTime() time.Time {
|
||||
return s.lastUpdated
|
||||
}
|
||||
|
||||
func (s *abstractRuleSet) String() string {
|
||||
return strings.Join(F.MapToString(s.rules), " ")
|
||||
}
|
||||
@ -129,18 +147,21 @@ func (s *abstractRuleSet) loadBytes(content []byte) error {
|
||||
|
||||
func (s *abstractRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
||||
rules := make([]adapter.HeadlessRule, len(headlessRules))
|
||||
var err error
|
||||
var ruleCount uint64
|
||||
for i, ruleOptions := range headlessRules {
|
||||
rules[i], err = NewHeadlessRule(s.router, ruleOptions)
|
||||
rule, err := NewHeadlessRule(s.router, ruleOptions)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||
}
|
||||
rules[i] = rule
|
||||
ruleCount += rule.RuleCount()
|
||||
}
|
||||
var metadata adapter.RuleSetMetadata
|
||||
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
||||
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||
s.rules = rules
|
||||
s.ruleCount = ruleCount
|
||||
s.metadata = metadata
|
||||
return nil
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ func NewLocalRuleSet(ctx context.Context, router adapter.Router, logger logger.C
|
||||
router: router,
|
||||
logger: logger,
|
||||
tag: options.Tag,
|
||||
sType: options.Type,
|
||||
format: options.Format,
|
||||
},
|
||||
}
|
||||
if options.Type == C.RuleSetTypeInline {
|
||||
@ -41,7 +43,6 @@ func NewLocalRuleSet(ctx context.Context, router adapter.Router, logger logger.C
|
||||
return ruleSet, nil
|
||||
}
|
||||
ruleSet.path = options.Path
|
||||
ruleSet.format = options.Format
|
||||
path, err := ruleSet.getPath(options.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -89,6 +90,10 @@ func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback)
|
||||
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) Update(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) Close() error {
|
||||
s.rules = nil
|
||||
return common.Close(common.PtrOrNil(s.watcher))
|
||||
|
@ -157,6 +157,16 @@ func (s *RemoteRuleSet) update() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) Update(ctx context.Context) error {
|
||||
err := s.fetchOnce(log.ContextWithNewID(ctx), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if s.refs.Load() == 0 {
|
||||
s.rules = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext adapter.RuleSetStartContext) error {
|
||||
s.logger.DebugContext(ctx, "updating rule-set ", s.tag, " from URL: ", s.options.URL)
|
||||
var httpClient *http.Client
|
||||
|
Loading…
x
Reference in New Issue
Block a user