feat: sniffer's force-domain and skip-domain support rule-set: and geosite:

This commit is contained in:
wwqgtxx 2024-08-14 22:38:17 +08:00
parent 696b75ee37
commit 7fd0467aef
7 changed files with 166 additions and 112 deletions

View File

@ -8,7 +8,6 @@ import (
"github.com/metacubex/mihomo/common/nnip" "github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
) )
@ -36,8 +35,7 @@ type Pool struct {
offset netip.Addr offset netip.Addr
cycle bool cycle bool
mux sync.Mutex mux sync.Mutex
host *trie.DomainTrie[struct{}] host []C.Rule
rules []C.Rule
ipnet netip.Prefix ipnet netip.Prefix
store store store store
} }
@ -68,14 +66,8 @@ func (p *Pool) LookBack(ip netip.Addr) (string, bool) {
// ShouldSkipped return if domain should be skipped // ShouldSkipped return if domain should be skipped
func (p *Pool) ShouldSkipped(domain string) bool { func (p *Pool) ShouldSkipped(domain string) bool {
if p.host != nil { for _, rule := range p.host {
if p.host.Search(domain) != nil { if match, _ := rule.Match(&C.Metadata{Host: domain}); match {
return true
}
}
for _, rule := range p.rules {
metadata := &C.Metadata{Host: domain}
if match, _ := rule.Match(metadata); match {
return true return true
} }
} }
@ -164,9 +156,7 @@ func (p *Pool) restoreState() {
type Options struct { type Options struct {
IPNet netip.Prefix IPNet netip.Prefix
Host *trie.DomainTrie[struct{}] Host []C.Rule
Rules []C.Rule
// Size sets the maximum number of entries in memory // Size sets the maximum number of entries in memory
// and does not work if Persistence is true // and does not work if Persistence is true
@ -197,7 +187,6 @@ func New(options Options) (*Pool, error) {
offset: first.Prev(), offset: first.Prev(),
cycle: false, cycle: false,
host: options.Host, host: options.Host,
rules: options.Rules,
ipnet: options.IPNet, ipnet: options.IPNet,
} }
if options.Persistence { if options.Persistence {

View File

@ -9,6 +9,8 @@ import (
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant"
RP "github.com/metacubex/mihomo/rules/provider"
"github.com/sagernet/bbolt" "github.com/sagernet/bbolt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -154,7 +156,7 @@ func TestPool_Skip(t *testing.T) {
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: ipnet, IPNet: ipnet,
Size: 10, Size: 10,
Host: tree, Host: []C.Rule{RP.NewDomainSet(tree.NewDomainSet(), "")},
}) })
assert.Nil(t, err) assert.Nil(t, err)
defer os.Remove(tempfile) defer os.Remove(tempfile)

View File

@ -9,7 +9,6 @@ import (
"github.com/metacubex/mihomo/common/lru" "github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/sniffer" "github.com/metacubex/mihomo/constant/sniffer"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@ -26,17 +25,26 @@ var Dispatcher *SnifferDispatcher
type SnifferDispatcher struct { type SnifferDispatcher struct {
enable bool enable bool
sniffers map[sniffer.Sniffer]SnifferConfig sniffers map[sniffer.Sniffer]SnifferConfig
forceDomain *trie.DomainSet forceDomain []C.Rule
skipSNI *trie.DomainSet skipDomain []C.Rule
skipList *lru.LruCache[string, uint8] skipList *lru.LruCache[string, uint8]
forceDnsMapping bool forceDnsMapping bool
parsePureIp bool parsePureIp bool
} }
func (sd *SnifferDispatcher) shouldOverride(metadata *C.Metadata) bool { func (sd *SnifferDispatcher) shouldOverride(metadata *C.Metadata) bool {
return (metadata.Host == "" && sd.parsePureIp) || if metadata.Host == "" && sd.parsePureIp {
sd.forceDomain.Has(metadata.Host) || return true
(metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) }
if metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping {
return true
}
for _, rule := range sd.forceDomain {
if ok, _ := rule.Match(&C.Metadata{Host: metadata.Host}); ok {
return true
}
}
return false
} }
func (sd *SnifferDispatcher) UDPSniff(packet C.PacketAdapter) bool { func (sd *SnifferDispatcher) UDPSniff(packet C.PacketAdapter) bool {
@ -94,9 +102,11 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort) log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return false return false
} else { } else {
if sd.skipSNI.Has(host) { for _, rule := range sd.skipDomain {
log.Debugln("[Sniffer] Skip sni[%s]", host) if ok, _ := rule.Match(&C.Metadata{Host: host}); ok {
return false log.Debugln("[Sniffer] Skip sni[%s]", host)
return false
}
} }
sd.skipList.Delete(dst) sd.skipList.Delete(dst)
@ -187,12 +197,12 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
} }
func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig,
forceDomain *trie.DomainSet, skipSNI *trie.DomainSet, forceDomain []C.Rule, skipDomain []C.Rule,
forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) { forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{ dispatcher := SnifferDispatcher{
enable: true, enable: true,
forceDomain: forceDomain, forceDomain: forceDomain,
skipSNI: skipSNI, skipDomain: skipDomain,
skipList: lru.New(lru.WithSize[string, uint8](128), lru.WithAge[string, uint8](600)), skipList: lru.New(lru.WithSize[string, uint8](128), lru.WithAge[string, uint8](600)),
forceDnsMapping: forceDnsMapping, forceDnsMapping: forceDnsMapping,
parsePureIp: parsePureIp, parsePureIp: parsePureIp,

View File

@ -134,6 +134,13 @@ func (t *DomainTrie[T]) Foreach(fn func(domain string, data T) bool) {
} }
} }
func (t *DomainTrie[T]) IsEmpty() bool {
if t == nil {
return true
}
return t.root.isEmpty()
}
func recursion[T any](items []string, node *Node[T], fn func(domain string, data T) bool) bool { func recursion[T any](items []string, node *Node[T], fn func(domain string, data T) bool) bool {
for key, data := range node.getChildren() { for key, data := range node.getChildren() {
newItems := append([]string{key}, items...) newItems := append([]string{key}, items...)

View File

@ -164,8 +164,8 @@ type IPTables struct {
type Sniffer struct { type Sniffer struct {
Enable bool Enable bool
Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig
ForceDomain *trie.DomainSet ForceDomain []C.Rule
SkipDomain *trie.DomainSet SkipDomain []C.Rule
ForceDnsMapping bool ForceDnsMapping bool
ParsePureIp bool ParsePureIp bool
} }
@ -627,7 +627,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
} }
config.Sniffer, err = parseSniffer(rawCfg.Sniffer) config.Sniffer, err = parseSniffer(rawCfg.Sniffer, rules, ruleProviders)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1408,87 +1408,27 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, err return nil, err
} }
var host *trie.DomainTrie[struct{}] var fakeIPTrie *trie.DomainTrie[struct{}]
var fakeIPRules []C.Rule
// fake ip skip host filter
if len(cfg.FakeIPFilter) != 0 {
host = trie.New[struct{}]()
for _, domain := range cfg.FakeIPFilter {
if strings.Contains(strings.ToLower(domain), ",") {
if strings.Contains(domain, "geosite:") {
subkeys := strings.Split(domain, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, country := range subkeys {
found := false
for _, rule := range rules {
if rule.RuleType() == C.GEOSITE {
if strings.EqualFold(country, rule.Payload()) {
found = true
fakeIPRules = append(fakeIPRules, rule)
}
}
}
if !found {
rule, err := RC.NewGEOSITE(country, "")
if err != nil {
return nil, err
}
fakeIPRules = append(fakeIPRules, rule)
}
}
}
} else if strings.Contains(strings.ToLower(domain), "rule-set:") {
subkeys := strings.Split(domain, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, domainSetName := range subkeys {
if rp, ok := ruleProviders[domainSetName]; !ok {
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
} else {
switch rp.Behavior() {
case providerTypes.IPCIDR:
return nil, fmt.Errorf("rule provider type error, except domain,actual %s", rp.Behavior())
case providerTypes.Classical:
log.Warnln("%s provider is %s, only matching it contain domain rule", rp.Name(), rp.Behavior())
default:
}
}
rule, err := RP.NewRuleSet(domainSetName, "", true)
if err != nil {
return nil, err
}
fakeIPRules = append(fakeIPRules, rule)
}
} else {
_ = host.Insert(domain, struct{}{})
}
}
}
if len(dnsCfg.Fallback) != 0 { if len(dnsCfg.Fallback) != 0 {
if host == nil { fakeIPTrie = trie.New[struct{}]()
host = trie.New[struct{}]()
}
for _, fb := range dnsCfg.Fallback { for _, fb := range dnsCfg.Fallback {
if net.ParseIP(fb.Addr) != nil { if net.ParseIP(fb.Addr) != nil {
continue continue
} }
_ = host.Insert(fb.Addr, struct{}{}) _ = fakeIPTrie.Insert(fb.Addr, struct{}{})
} }
} }
if host != nil { // fake ip skip host filter
host.Optimize() host, err := parseDomain(cfg.FakeIPFilter, fakeIPTrie, rules, ruleProviders)
if err != nil {
return nil, err
} }
pool, err := fakeip.New(fakeip.Options{ pool, err := fakeip.New(fakeip.Options{
IPNet: fakeIPRange, IPNet: fakeIPRange,
Size: 1000, Size: 1000,
Host: host, Host: host,
Rules: fakeIPRules,
Persistence: rawCfg.Profile.StoreFakeIP, Persistence: rawCfg.Profile.StoreFakeIP,
}) })
if err != nil { if err != nil {
@ -1609,7 +1549,7 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
return nil return nil
} }
func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) { func parseSniffer(snifferRaw RawSniffer, rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*Sniffer, error) {
sniffer := &Sniffer{ sniffer := &Sniffer{
Enable: snifferRaw.Enable, Enable: snifferRaw.Enable,
ForceDnsMapping: snifferRaw.ForceDnsMapping, ForceDnsMapping: snifferRaw.ForceDnsMapping,
@ -1672,23 +1612,83 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
sniffer.Sniffers = loadSniffer sniffer.Sniffers = loadSniffer
forceDomainTrie := trie.New[struct{}]() forceDomain, err := parseDomain(snifferRaw.ForceDomain, nil, rules, ruleProviders)
for _, domain := range snifferRaw.ForceDomain { if err != nil {
err := forceDomainTrie.Insert(domain, struct{}{}) return nil, fmt.Errorf("error in force-domain, error:%w", err)
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
} }
sniffer.ForceDomain = forceDomainTrie.NewDomainSet() sniffer.ForceDomain = forceDomain
skipDomainTrie := trie.New[struct{}]() skipDomain, err := parseDomain(snifferRaw.SkipDomain, nil, rules, ruleProviders)
for _, domain := range snifferRaw.SkipDomain { if err != nil {
err := skipDomainTrie.Insert(domain, struct{}{}) return nil, fmt.Errorf("error in skip-domain, error:%w", err)
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
} }
sniffer.SkipDomain = skipDomainTrie.NewDomainSet() sniffer.SkipDomain = skipDomain
return sniffer, nil return sniffer, nil
} }
func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (domainRules []C.Rule, err error) {
var rule C.Rule
for _, domain := range domains {
domainLower := strings.ToLower(domain)
if strings.Contains(domainLower, "geosite:") {
subkeys := strings.Split(domain, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, country := range subkeys {
found := false
for _, rule = range rules {
if rule.RuleType() == C.GEOSITE {
if strings.EqualFold(country, rule.Payload()) {
found = true
domainRules = append(domainRules, rule)
}
}
}
if !found {
rule, err = RC.NewGEOSITE(country, "")
if err != nil {
return nil, err
}
domainRules = append(domainRules, rule)
}
}
} else if strings.Contains(domainLower, "rule-set:") {
subkeys := strings.Split(domain, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, domainSetName := range subkeys {
if rp, ok := ruleProviders[domainSetName]; !ok {
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
} else {
switch rp.Behavior() {
case providerTypes.IPCIDR:
return nil, fmt.Errorf("rule provider type error, except domain,actual %s", rp.Behavior())
case providerTypes.Classical:
log.Warnln("%s provider is %s, only matching it contain domain rule", rp.Name(), rp.Behavior())
default:
}
}
rule, err = RP.NewRuleSet(domainSetName, "", true)
if err != nil {
return nil, err
}
domainRules = append(domainRules, rule)
}
} else {
if domainTrie == nil {
domainTrie = trie.New[struct{}]()
}
err = domainTrie.Insert(domain, struct{}{})
if err != nil {
return nil, err
}
}
}
if !domainTrie.IsEmpty() {
rule = RP.NewDomainSet(domainTrie.NewDomainSet(), "")
domainRules = append(domainRules, rule)
}
return
}

View File

@ -27,6 +27,7 @@ const (
ProcessNameRegex ProcessNameRegex
ProcessPathRegex ProcessPathRegex
RuleSet RuleSet
DomainSet
Network Network
Uid Uid
SubRules SubRules
@ -90,6 +91,8 @@ func (rt RuleType) String() string {
return "Match" return "Match"
case RuleSet: case RuleSet:
return "RuleSet" return "RuleSet"
case DomainSet:
return "DomainSet"
case Network: case Network:
return "Network" return "Network"
case DSCP: case DSCP:

View File

@ -0,0 +1,43 @@
package provider
import (
"github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant"
)
type DomainSet struct {
*domainStrategy
adapter string
}
func (d *DomainSet) ProviderNames() []string {
return nil
}
func (d *DomainSet) RuleType() C.RuleType {
return C.DomainSet
}
func (d *DomainSet) Match(metadata *C.Metadata) (bool, string) {
if d.domainSet == nil {
return false, ""
}
return d.domainSet.Has(metadata.RuleHost()), d.adapter
}
func (d *DomainSet) Adapter() string {
return d.adapter
}
func (d *DomainSet) Payload() string {
return ""
}
func NewDomainSet(domainSet *trie.DomainSet, adapter string) *DomainSet {
return &DomainSet{
domainStrategy: &domainStrategy{domainSet: domainSet},
adapter: adapter,
}
}
var _ C.Rule = (*DomainSet)(nil)