diff --git a/component/fakeip/skipper.go b/component/fakeip/skipper.go index 2df4094a..d872fe44 100644 --- a/component/fakeip/skipper.go +++ b/component/fakeip/skipper.go @@ -4,13 +4,29 @@ import ( C "github.com/metacubex/mihomo/constant" ) +const ( + UseFakeIP = "fake-ip" + UseRealIP = "real-ip" +) + type Skipper struct { - Host []C.DomainMatcher - Mode C.FilterMode + Rules []C.Rule + Host []C.DomainMatcher + Mode C.FilterMode } // ShouldSkipped return if domain should be skipped func (p *Skipper) ShouldSkipped(domain string) bool { + if len(p.Rules) > 0 { + metadata := &C.Metadata{Host: domain} + for _, rule := range p.Rules { + if matched, action := rule.Match(metadata, C.RuleMatchHelper{}); matched { + return action == UseRealIP + } + } + return false + } + should := p.shouldSkipped(domain) if p.Mode == C.FilterWhiteList { return !should diff --git a/config/config.go b/config/config.go index c2b5664d..31f54728 100644 --- a/config/config.go +++ b/config/config.go @@ -1450,16 +1450,22 @@ func parseDNS(rawCfg *RawConfig, ruleProviders map[string]P.RuleProvider) (*DNS, } } - // fake ip skip host filter - host, err := parseDomain(cfg.FakeIPFilter, fakeIPTrie, "dns.fake-ip-filter", ruleProviders) - if err != nil { - return nil, err + skipper := &fakeip.Skipper{Mode: cfg.FakeIPFilterMode} + + if cfg.FakeIPFilterMode == C.FilterRule { + rules, err := parseFakeIPRules(cfg.FakeIPFilter, ruleProviders) + if err != nil { + return nil, err + } + skipper.Rules = rules + } else { + host, err := parseDomain(cfg.FakeIPFilter, fakeIPTrie, "dns.fake-ip-filter", ruleProviders) + if err != nil { + return nil, err + } + skipper.Host = host } - skipper := &fakeip.Skipper{ - Host: host, - Mode: cfg.FakeIPFilterMode, - } dnsCfg.FakeIPSkipper = skipper dnsCfg.FakeIPTTL = cfg.FakeIPTTL @@ -1541,6 +1547,55 @@ func parseDNS(rawCfg *RawConfig, ruleProviders map[string]P.RuleProvider) (*DNS, return dnsCfg, nil } +func parseFakeIPRules(rawRules []string, ruleProviders map[string]P.RuleProvider) ([]C.Rule, error) { + var rules []C.Rule + + for idx, line := range rawRules { + tp, payload, action, params := RC.ParseRulePayload(line, true) + + action = strings.ToLower(action) + if action != fakeip.UseFakeIP && action != fakeip.UseRealIP { + return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: invalid action '%s', must be 'fake-ip' or 'real-ip'", idx, line, action) + } + + if tp == "RULE-SET" { + if rp, ok := ruleProviders[payload]; !ok { + return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: rule-set '%s' not found", idx, line, payload) + } else { + switch rp.Behavior() { + case P.IPCIDR: + return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: rule-set behavior is %s, must be domain or classical", idx, line, rp.Behavior()) + case P.Classical: + log.Warnln("%s provider is %s, only matching domain rules in fake-ip-filter", rp.Name(), rp.Behavior()) + default: + } + } + } + + parsed, err := R.ParseRule(tp, payload, action, params, nil) + if err != nil { + return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: %w", idx, line, err) + } + + if !isDomainRule(parsed.RuleType()) && parsed.RuleType() != C.MATCH { + return nil, fmt.Errorf("dns.fake-ip-filter[%d] [%s] error: rule type '%s' not supported, only domain-based rules allowed", idx, line, tp) + } + + rules = append(rules, parsed) + } + + return rules, nil +} + +func isDomainRule(rt C.RuleType) bool { + switch rt { + case C.Domain, C.DomainSuffix, C.DomainKeyword, C.DomainRegex, C.DomainWildcard, C.GEOSITE, C.RuleSet: + return true + default: + return false + } +} + func parseAuthentication(rawRecords []string) []auth.AuthUser { var users []auth.AuthUser for _, line := range rawRecords { diff --git a/constant/dns.go b/constant/dns.go index 79dafd2b..3b653be2 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -103,6 +103,7 @@ func (d *DNSPrefer) UnmarshalText(data []byte) error { var FilterModeMapping = map[string]FilterMode{ FilterBlackList.String(): FilterBlackList, FilterWhiteList.String(): FilterWhiteList, + FilterRule.String(): FilterRule, } type FilterMode int @@ -110,6 +111,7 @@ type FilterMode int const ( FilterBlackList FilterMode = iota FilterWhiteList + FilterRule ) func (e FilterMode) String() string { @@ -118,6 +120,8 @@ func (e FilterMode) String() string { return "blacklist" case FilterWhiteList: return "whitelist" + case FilterRule: + return "rule" default: return "unknown" } diff --git a/docs/config.yaml b/docs/config.yaml index a350d1af..80e1c8af 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -272,8 +272,20 @@ dns: - rule-set:fakeip-filter # fakeip-filter 为 geosite 中名为 fakeip-filter 的分类(需要自行保证该分类存在) - geosite:fakeip-filter + + # 当 fake-ip-filter-mode: rule 时开启规则模式 + # fake-ip 与路由 rules 匹配逻辑一致(自上而下),语法也一致,支持GEOSITE、RuleSet、DOMAIN*、MATCH + - RULE-SET,reject-domain,fake-ip # 自定义 RuleSet behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则 + - RULE-SET,proxy-domain,fake-ip + - GEOSITE,gfw,fake-ip + - DOMAIN,www.baidu.com,real-ip + - DOMAIN-SUFFIX,qq.com,real-ip + - DOMAIN-SUFFIX,jd.com,fake-ip + - MATCH,fake-ip # 最后 fake-ip or real-ip + # 配置fake-ip-filter的匹配模式,默认为blacklist,即如果匹配成功不返回fake-ip # 可设置为whitelist,即只有匹配成功才返回fake-ip + # 也可配置为rule,规则模式语法见fake-ip-filter说明 fake-ip-filter-mode: blacklist # 配置fakeip查询返回的TTL,非必要情况下请勿修改 fake-ip-ttl: 1