/* * SPDX-License-Identifier: AGPL-3.0-only * Copyright (c) 2022-2024, daeuniverse Organization */ package domain_matcher import ( "fmt" "hash/fnv" "math/rand" "reflect" "testing" "github.com/daeuniverse/dae/common/assets" "github.com/daeuniverse/dae/common/consts" "github.com/daeuniverse/dae/component/routing" "github.com/daeuniverse/dae/config" "github.com/daeuniverse/dae/pkg/config_parser" "github.com/sirupsen/logrus" ) var TestSample = []string{ "9game.cn", "aliapp.org", "alibaba-inc.com", "alibaba.com", "alibabacapital.com", "alibabacorp.com", "alibabadoctor.com", "alibabafuturehotel.com", "alibabagroup.com", "alibabaplanet.com", "alibabaued.com", "alibabausercontent.com", "alifanyi.com", "alihealth.com.cn", "alihealth.hk", "aliimg.com", "51y5.net", "a.adtng.com", "aaxads.com", "addthisedge.com", "adtrue.com", "ad-srv.net", "ad.api.moji.com", "ad.wang502.com", "adbutter.net", "ads.trafficjunky.net", "adtechus.com", "adxprtz.com", "cdn.advertserve.com", "cdn.banclip.com", "cfts1tifqr.com", "contentabc.com", "cretgate.com", "ero-advertising.com", "eroadvertising.com", "exoclick.com", "exosrv.com", "go2.global", "img-bss.csdn.net", "imglnkc.com", "imglnkd.com", "innovid.com", "ja2.gamersky.com", "jl3.yjaxa.top", "juicyads.com", "kepler-37b.com", "lqc006.com", "moat.com", "moatads.com", "realsrv.com", "s4yxaqyq95.com", "shhs-ydd8x2.yjrmss.cn", "static.javhd.com", "tm-banners.gamingadult.com", "trafficfactory.biz", "tsyndicate.com", "abchina.com", "bankcomm.com", "bankofbeijing.com.cn", "bosc.cn", "bsb.com.cn", "ccb.com", "cgbchina.com.cn", "cib.com.cn", "citibank.com.cn", "cmbc.com.cn", "hsbc.com.cn", "hxb.com.cn", "njcb.com.cn", "psbc.com", "spdb.com.cn", "whccb.com", "_https._tcp.mirrors.ustc.edu.cn", "ipv4.master.test-ipv6.com", } type RoutingMatcherBuilder struct { simulatedDomainSet []routing.DomainSet Fallback string err error } func (b *RoutingMatcherBuilder) OutboundToId(outbound string) uint8 { h := fnv.New64() h.Write([]byte(outbound)) return uint8(h.Sum64() & 0xFF) } func (b *RoutingMatcherBuilder) AddDomain(f *config_parser.Function, key string, values []string, outbound *routing.Outbound) { if b.err != nil { return } switch consts.RoutingDomainKey(key) { case consts.RoutingDomainKey_Regex, consts.RoutingDomainKey_Full, consts.RoutingDomainKey_Keyword, consts.RoutingDomainKey_Suffix: default: b.err = fmt.Errorf("addDomain: unsupported key: %v", key) return } b.simulatedDomainSet = append(b.simulatedDomainSet, routing.DomainSet{ Key: consts.RoutingDomainKey(key), RuleIndex: len(b.simulatedDomainSet), Domains: values, }) } func getDomain() (simulatedDomainSet []routing.DomainSet, err error) { var rules []*config_parser.RoutingRule sections, err := config_parser.Parse(` routing { domain(suffix: test-ipv6.com)->direct domain(geosite:bing)->us domain(_https._tcp.mirrors.ustc.edu.cn)->us domain(full:dns.google.com) -> direct domain(geosite:category-ads-all) -> block domain(geosite:cn) -> direct }`) if err != nil { return nil, err } var r config.Routing if err = config.SectionParser(reflect.ValueOf(&r), sections[0]); err != nil { return nil, err } if rules, err = routing.ApplyRulesOptimizers(r.Rules, &routing.AliasOptimizer{}, &routing.DatReaderOptimizer{Logger: logrus.StandardLogger(), LocationFinder: assets.NewLocationFinder(nil)}, &routing.MergeAndSortRulesOptimizer{}, &routing.DeduplicateParamsOptimizer{}, ); err != nil { return nil, fmt.Errorf("ApplyRulesOptimizers error:\n%w", err) } builder := RoutingMatcherBuilder{} rb := routing.NewRulesBuilder(logrus.StandardLogger()) rb.RegisterFunctionParser("domain", func(log *logrus.Logger, f *config_parser.Function, key string, paramValueGroup []string, overrideOutbound *routing.Outbound) (err error) { builder.AddDomain(f, key, paramValueGroup, overrideOutbound) return nil }) if err = rb.Apply(rules); err != nil { return nil, fmt.Errorf("Apply: %w", err) } return builder.simulatedDomainSet, nil } func BenchmarkBruteforce(b *testing.B) { b.StopTimer() logrus.SetLevel(logrus.WarnLevel) simulatedDomainSet, err := getDomain() if err != nil { b.Fatal(err) } bf := NewBruteforce(consts.MaxMatchSetLen) for _, domains := range simulatedDomainSet { bf.AddSet(domains.RuleIndex, domains.Domains, domains.Key) } if err = bf.Build(); err != nil { b.Fatal(err) } b.StartTimer() runBenchmark(b, bf) } func BenchmarkGoRegexpNfa(b *testing.B) { b.StopTimer() logrus.SetLevel(logrus.WarnLevel) simulatedDomainSet, err := getDomain() if err != nil { b.Fatal(err) } nfa := NewGoRegexpNfa(consts.MaxMatchSetLen) for _, domains := range simulatedDomainSet { nfa.AddSet(domains.RuleIndex, domains.Domains, domains.Key) } if err = nfa.Build(); err != nil { b.Fatal(err) } b.StartTimer() runBenchmark(b, nfa) } func BenchmarkAhocorasickSlimtrie(b *testing.B) { b.StopTimer() logrus.SetLevel(logrus.WarnLevel) simulatedDomainSet, err := getDomain() if err != nil { b.Fatal(err) } ahocorasick := NewAhocorasickSlimtrie(logrus.StandardLogger(), consts.MaxMatchSetLen) for _, domains := range simulatedDomainSet { ahocorasick.AddSet(domains.RuleIndex, domains.Domains, domains.Key) } if err = ahocorasick.Build(); err != nil { b.Fatal(err) } b.StartTimer() runBenchmark(b, ahocorasick) } func runBenchmark(b *testing.B, matcher routing.DomainMatcher) { rand.Seed(100) for i := 0; i < b.N; i++ { sample := TestSample[rand.Intn(len(TestSample))] choice := rand.Intn(10) switch { case choice < 4: addN := rand.Intn(5) buf := make([]byte, addN) for i := range buf { buf[i] = 'a' + byte(rand.Intn('z'-'a')) } sample = string(buf) + "." + sample case choice >= 4 && choice < 6: k := rand.Intn(len(sample)) sample = sample[k:] default: } matcher.MatchDomainBitmap(sample) } }