mosdns/plugin/executable/sequence/chain.go
dengxiongjian cd761e8145
Some checks are pending
Test mosdns / build (push) Waiting to run
新增Mikrotik API 插入解析ip
2025-07-31 11:28:55 +08:00

238 lines
5.5 KiB
Go

/*
* Copyright (C) 2020-2022, IrineSistiana
*
* This file is part of mosdns.
*
* mosdns is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* mosdns is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package sequence
import (
"context"
"errors"
"fmt"
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
"io"
)
type ChainNode struct {
Matches []Matcher // Can be empty, indicates this node has no match specified.
// At least one of E or RE must not nil.
// In case both are set. E is preferred.
E Executable
RE RecursiveExecutable
}
type ChainWalker struct {
p int
chain []*ChainNode
jumpBack *ChainWalker
}
func NewChainWalker(chain []*ChainNode, jumpBack *ChainWalker) ChainWalker {
return ChainWalker{
chain: chain,
jumpBack: jumpBack,
}
}
func (w *ChainWalker) ExecNext(ctx context.Context, qCtx *query_context.Context) error {
p := w.p
// Evaluate rules' matchers in loop.
checkMatchesLoop:
for p < len(w.chain) {
n := w.chain[p]
for _, match := range n.Matches {
ok, err := match.Match(ctx, qCtx)
if err != nil {
return err
}
if !ok {
// Skip this node if condition was not matched.
p++
continue checkMatchesLoop
}
}
// Exec rules' executables in loop, or in stack if it is a recursive executable.
switch {
case n.E != nil:
if err := n.E.Exec(ctx, qCtx); err != nil {
return err
}
p++
continue
case n.RE != nil:
next := ChainWalker{
p: p + 1,
chain: w.chain,
jumpBack: w.jumpBack,
}
return n.RE.Exec(ctx, qCtx, next)
default:
panic("n cannot be executed")
}
}
if w.jumpBack != nil { // End of chain, time to jump back.
return w.jumpBack.ExecNext(ctx, qCtx)
}
// EoC.
return nil
}
func (w *ChainWalker) nop() bool {
return w.p >= len(w.chain)
}
func (s *Sequence) buildChain(bq BQ, rs []RuleConfig) error {
c := make([]*ChainNode, 0, len(rs))
for ri, r := range rs {
n, err := s.newNode(bq, r, ri)
if err != nil {
return fmt.Errorf("failed to init rule #%d, %w", ri, err)
}
c = append(c, n)
}
s.chain = c
return nil
}
func (s *Sequence) newNode(bq BQ, r RuleConfig, ri int) (*ChainNode, error) {
n := new(ChainNode)
// init matches
for mi, mc := range r.Matches {
m, err := s.newMatcher(bq, mc, ri, mi)
if err != nil {
return nil, fmt.Errorf("failed to init matcher #%d, %w", mi, err)
}
n.Matches = append(n.Matches, m)
}
// init exec
e, re, err := s.newExec(bq, r, ri)
if err != nil {
return nil, fmt.Errorf("failed to init exec, %w", err)
}
n.E = e
n.RE = re
return n, nil
}
func (s *Sequence) newMatcher(bq BQ, mc MatchConfig, ri, mi int) (Matcher, error) {
var m Matcher
switch {
case len(mc.Tag) > 0:
m, _ = bq.M().GetPlugin(mc.Tag).(Matcher)
if m == nil {
return nil, fmt.Errorf("can not find matcher %s", mc.Tag)
}
if qc, ok := m.(QuickConfigurableMatch); ok {
v, err := qc.QuickConfigureMatch(mc.Args)
if err != nil {
return nil, fmt.Errorf("fail to configure plugin %s, %w", mc.Tag, err)
}
m = v
}
case len(mc.Type) > 0:
f := GetMatchQuickSetup(mc.Type)
if f == nil {
return nil, fmt.Errorf("invalid matcher type %s", mc.Type)
}
p, err := f(NewBQ(bq.M(), bq.L().Named(fmt.Sprintf("r%d.m%d", ri, mi))), mc.Args)
if err != nil {
return nil, fmt.Errorf("failed to init matcher, %w", err)
}
s.anonymousPlugins = append(s.anonymousPlugins, p)
m = p
}
if m == nil {
return nil, errors.New("missing args")
}
if mc.Reverse {
m = reverseMatcher(m)
}
return m, nil
}
func (s *Sequence) newExec(bq BQ, rc RuleConfig, ri int) (Executable, RecursiveExecutable, error) {
var exec any
switch {
case len(rc.Tag) > 0:
p := bq.M().GetPlugin(rc.Tag)
if p == nil {
return nil, nil, fmt.Errorf("can not find executable %s", rc.Tag)
}
if qc, ok := p.(QuickConfigurableExec); ok {
v, err := qc.QuickConfigureExec(rc.Args)
if err != nil {
return nil, nil, fmt.Errorf("fail to configure plugin %s, %w", rc.Tag, err)
}
exec = v
} else {
exec = p
}
case len(rc.Type) > 0:
f := GetExecQuickSetup(rc.Type)
if f == nil {
return nil, nil, fmt.Errorf("invalid executable type %s", rc.Type)
}
v, err := f(NewBQ(bq.M(), bq.L().Named(fmt.Sprintf("r%d", ri))), rc.Args)
if err != nil {
return nil, nil, fmt.Errorf("failed to init executable, %w", err)
}
s.anonymousPlugins = append(s.anonymousPlugins, v)
exec = v
default:
return nil, nil, errors.New("missing args")
}
e, _ := exec.(Executable)
re, _ := exec.(RecursiveExecutable)
if re == nil && e == nil {
return nil, nil, errors.New("invalid args, initialized object is not executable")
}
return e, re, nil
}
func closePlugin(p any) {
if c, ok := p.(io.Closer); ok {
_ = c.Close()
}
}
func reverseMatcher(m Matcher) Matcher {
return reverseMatch{m: m}
}
type reverseMatch struct {
m Matcher
}
func (r reverseMatch) Match(ctx context.Context, qCtx *query_context.Context) (bool, error) {
ok, err := r.m.Match(ctx, qCtx)
if err != nil {
return false, err
}
return !ok, nil
}