Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rule): support inline rule provider #1731

Merged
merged 1 commit into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions constant/provider/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
File VehicleType = iota
HTTP
Compatible
Inline
)

// VehicleType defined
Expand All @@ -26,6 +27,8 @@ func (v VehicleType) String() string {
return "HTTP"
case Compatible:
return "Compatible"
case Inline:
return "Inline"
default:
return "Unknown"
}
Expand Down
8 changes: 8 additions & 0 deletions docs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 14 additions & 9 deletions rules/provider/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down
132 changes: 110 additions & 22 deletions rules/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand All @@ -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(),
})
}

Expand All @@ -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) {
Expand All @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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
}