feat: support inline rule provider (#1731)

This commit is contained in:
qianlongzt 2024-12-19 09:16:45 +08:00 committed by GitHub
parent 3f6823ba49
commit bb803249fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 135 additions and 31 deletions

View File

@ -13,6 +13,7 @@ const (
File VehicleType = iota
HTTP
Compatible
Inline
)
// VehicleType defined
@ -26,6 +27,8 @@ func (v VehicleType) String() string {
return "HTTP"
case Compatible:
return "Compatible"
case Inline:
return "Inline"
default:
return "Unknown"
}

View File

@ -1014,6 +1014,14 @@ rule-providers:
format: mrs
behavior: domain
path: /path/to/save/file.mrs
rule4:
type: inline
behavior: domain # classical / ipcidr
payload:
- '.blogger.com'
- '*.*.microsoft.com'
- 'books.itunes.apple.com'
rules:
- RULE-SET,rule1,REJECT
- IP-ASN,1,PROXY

View File

@ -16,17 +16,20 @@ var (
)
type ruleProviderSchema struct {
Type string `provider:"type"`
Behavior string `provider:"behavior"`
Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"`
Format string `provider:"format,omitempty"`
Interval int `provider:"interval,omitempty"`
SizeLimit int64 `provider:"size-limit,omitempty"`
Type string `provider:"type"`
Behavior string `provider:"behavior"`
Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"`
Format string `provider:"format,omitempty"`
Interval int `provider:"interval,omitempty"`
SizeLimit int64 `provider:"size-limit,omitempty"`
Payload []string `provider:"payload,omitempty"`
}
func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) {
type parseRuleFunc func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)
func ParseRuleProvider(name string, mapping map[string]any, parse parseRuleFunc) (P.RuleProvider, error) {
schema := &ruleProviderSchema{}
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
if err := decoder.Decode(mapping, schema); err != nil {
@ -55,6 +58,8 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
}
}
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil, resource.DefaultHttpTimeout, schema.SizeLimit)
case "inline":
return newInlineProvider(name, behavior, schema.Payload, parse), nil
default:
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
}

View File

@ -24,9 +24,13 @@ func SetTunnel(t P.Tunnel) {
}
type ruleSetProvider struct {
ruleSetProviderBase
*resource.Fetcher[ruleStrategy]
format P.RuleFormat
}
type ruleSetProviderBase struct {
behavior P.RuleBehavior
format P.RuleFormat
strategy ruleStrategy
}
@ -61,7 +65,7 @@ type mrsRuleStrategy interface {
DumpMrs(f func(key string) bool)
}
func (rp *ruleSetProvider) Type() P.ProviderType {
func (rp *ruleSetProviderBase) Type() P.ProviderType {
return P.Rule
}
@ -75,40 +79,51 @@ func (rp *ruleSetProvider) Update() error {
return err
}
func (rp *ruleSetProvider) Behavior() P.RuleBehavior {
func (rp *ruleSetProviderBase) Behavior() P.RuleBehavior {
return rp.behavior
}
func (rp *ruleSetProvider) Count() int {
func (rp *ruleSetProviderBase) Count() int {
return rp.strategy.Count()
}
func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool {
func (rp *ruleSetProviderBase) Match(metadata *C.Metadata) bool {
return rp.strategy != nil && rp.strategy.Match(metadata)
}
func (rp *ruleSetProvider) ShouldResolveIP() bool {
func (rp *ruleSetProviderBase) ShouldResolveIP() bool {
return rp.strategy.ShouldResolveIP()
}
func (rp *ruleSetProvider) ShouldFindProcess() bool {
func (rp *ruleSetProviderBase) ShouldFindProcess() bool {
return rp.strategy.ShouldFindProcess()
}
func (rp *ruleSetProvider) Strategy() any {
func (rp *ruleSetProviderBase) Strategy() any {
return rp.strategy
}
type providerForApi struct {
Behavior string `json:"behavior"`
Format string `json:"format"`
Name string `json:"name"`
RuleCount int `json:"ruleCount"`
Type string `json:"type"`
VehicleType string `json:"vehicleType"`
UpdatedAt time.Time `json:"updatedAt"`
Payload []string `json:"payload,omitempty"`
}
func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(
map[string]interface{}{
"behavior": rp.behavior.String(),
"format": rp.format.String(),
"name": rp.Name(),
"ruleCount": rp.strategy.Count(),
"type": rp.Type().String(),
"updatedAt": rp.UpdatedAt(),
"vehicleType": rp.VehicleType().String(),
providerForApi{
Behavior: rp.behavior.String(),
Format: rp.format.String(),
Name: rp.Fetcher.Name(),
RuleCount: rp.strategy.Count(),
Type: rp.Type().String(),
UpdatedAt: rp.UpdatedAt(),
VehicleType: rp.VehicleType().String(),
})
}
@ -118,10 +133,13 @@ func (rp *RuleSetProvider) Close() error {
}
func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleFormat, interval time.Duration, vehicle P.Vehicle,
parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) P.RuleProvider {
parse parseRuleFunc,
) P.RuleProvider {
rp := &ruleSetProvider{
behavior: behavior,
format: format,
ruleSetProviderBase: ruleSetProviderBase{
behavior: behavior,
},
format: format,
}
onUpdate := func(strategy ruleStrategy) {
@ -142,7 +160,7 @@ func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleForma
return wrapper
}
func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy {
func newStrategy(behavior P.RuleBehavior, parse parseRuleFunc) ruleStrategy {
switch behavior {
case P.Domain:
strategy := NewDomainStrategy()
@ -158,8 +176,10 @@ func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string,
}
}
var ErrNoPayload = errors.New("file must have a `payload` field")
var ErrInvalidFormat = errors.New("invalid format")
var (
ErrNoPayload = errors.New("file must have a `payload` field")
ErrInvalidFormat = errors.New("invalid format")
)
func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStrategy, error) {
strategy.Reset()
@ -254,3 +274,71 @@ func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStr
return strategy, nil
}
func rulesParseInline(rs []string, strategy ruleStrategy) ruleStrategy {
strategy.Reset()
for _, r := range rs {
if r != "" {
strategy.Insert(r)
}
}
strategy.FinishInsert()
return strategy
}
type inlineProvider struct {
ruleSetProviderBase
name string
updateTime time.Time
payload []string
}
func (i *inlineProvider) Name() string {
return i.name
}
func (i *inlineProvider) Initial() error {
return nil
}
func (i *inlineProvider) Update() error {
// make api update happy
i.updateTime = time.Now()
return nil
}
func (i *inlineProvider) VehicleType() P.VehicleType {
return P.Inline
}
func (i *inlineProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(
providerForApi{
Behavior: i.behavior.String(),
Name: i.Name(),
RuleCount: i.strategy.Count(),
Type: i.Type().String(),
VehicleType: i.VehicleType().String(),
UpdatedAt: i.updateTime,
Payload: i.payload,
})
}
func newInlineProvider(
name string,
behavior P.RuleBehavior,
payload []string,
parse parseRuleFunc,
) P.RuleProvider {
rp := &inlineProvider{
ruleSetProviderBase: ruleSetProviderBase{
behavior: behavior,
strategy: newStrategy(behavior, parse),
},
payload: payload,
name: name,
updateTime: time.Now(),
}
rp.strategy = rulesParseInline(payload, rp.strategy)
return rp
}