From 8f6b0a6e2ab9ea818e5dc9ecb9281bf250b49cad Mon Sep 17 00:00:00 2001 From: mzz <30586220+mzz2017@users.noreply.github.com> Date: Sat, 18 Feb 2023 18:27:28 +0800 Subject: [PATCH] feat/optimize: add userspace routing and optimize domain routing (#18) --- Makefile | 2 +- .../generate_bpf_objects/dummy/dummy.go | 2 +- .../generate_bpf_objects.go | 2 +- cmd/validate.go | 2 +- common/assets/assets.go | 2 +- common/consts/control.go | 2 +- common/consts/dialer.go | 2 +- common/consts/ebpf.go | 7 +- common/consts/routing.go | 12 +- common/debug.go | 2 +- common/netutils/context_dialer.go | 2 +- common/netutils/dns.go | 2 +- common/netutils/ip46.go | 2 +- common/netutils/url.go | 2 +- common/utils.go | 2 +- component/outbound/dialer/alive_dialer_set.go | 2 +- component/outbound/dialer/block.go | 2 +- .../outbound/dialer/connectivity_check.go | 2 +- component/outbound/dialer/latencies_n.go | 2 +- component/outbound/dialer/register.go | 2 +- component/outbound/dialer/sockopt.go | 2 +- component/outbound/dialer_group.go | 2 +- component/outbound/dialer_group_test.go | 2 +- component/outbound/dialer_selection_policy.go | 2 +- component/outbound/filter.go | 2 +- component/outbound/outbound.go | 2 +- component/routing/domain_matcher.go | 14 ++ .../routing/domain_matcher/ahocorasick.go | 119 +++++++++ .../routing/domain_matcher/benchmark_test.go | 236 ++++++++++++++++++ .../routing/domain_matcher/bruteforce.go | 67 +++++ .../routing/domain_matcher/go_regexp_nfa.go | 93 +++++++ component/routing/matcher_builder.go | 10 +- component/routing/optimizer.go | 14 +- component/sniffing/conn_sniffer.go | 2 +- component/sniffing/http.go | 2 +- .../sniffing/internal/quicutils/binary.go | 2 +- .../sniffing/internal/quicutils/cipher.go | 2 +- .../internal/quicutils/cipher_test.go | 2 +- .../sniffing/internal/quicutils/relocation.go | 2 +- .../sniffing/internal/quicutils/version.go | 2 +- component/sniffing/quic.go | 2 +- component/sniffing/quic_bench_test.go | 2 +- component/sniffing/sniffer.go | 2 +- component/sniffing/sniffing.go | 2 +- component/sniffing/sniffing_bench_test.go | 2 +- component/sniffing/tls.go | 2 +- component/sniffing/tls_test.go | 2 +- config/config.go | 2 +- config/config_merger.go | 2 +- config/parser.go | 2 +- config/patch.go | 2 +- control/addr.go | 2 +- control/bpf_subobjects.go | 2 +- control/bpf_utils.go | 8 +- control/connectivity.go | 2 +- control/control.go | 2 +- control/control_plane.go | 36 +-- control/control_plane_core.go | 2 +- control/dns.go | 13 +- control/dns_upstream.go | 2 +- control/domain_match.go | 48 ---- control/kern/tproxy.c | 2 +- control/routing_matcher_builder.go | 81 ++++-- control/routing_matcher_userspace.go | 150 +++++++++++ control/tcp.go | 2 +- control/tproxy_utils.go | 2 +- control/udp.go | 2 +- control/udp_endpoint.go | 2 +- go.mod | 2 + go.sum | 4 + main.go | 2 +- pkg/config_parser/config_parser.go | 2 +- pkg/config_parser/config_parser_test.go | 2 +- pkg/config_parser/error.go | 2 +- pkg/config_parser/section.go | 2 +- pkg/config_parser/walker.go | 2 +- pkg/geodata/decode.go | 2 +- pkg/geodata/geodata.go | 2 +- pkg/logger/logger.go | 2 +- 79 files changed, 862 insertions(+), 176 deletions(-) create mode 100644 component/routing/domain_matcher.go create mode 100644 component/routing/domain_matcher/ahocorasick.go create mode 100644 component/routing/domain_matcher/benchmark_test.go create mode 100644 component/routing/domain_matcher/bruteforce.go create mode 100644 component/routing/domain_matcher/go_regexp_nfa.go delete mode 100644 control/domain_match.go create mode 100644 control/routing_matcher_userspace.go diff --git a/Makefile b/Makefile index c92c3f8..2e4c358 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # # SPDX-License-Identifier: AGPL-3.0-only -# Copyright (c) since 2022, v2rayA Organization +# Copyright (c) 2022-2023, v2rayA Organization # # The development version of clang is distributed as the 'clang' binary, diff --git a/cmd/internal/generate_bpf_objects/dummy/dummy.go b/cmd/internal/generate_bpf_objects/dummy/dummy.go index ee28cf3..a216156 100644 --- a/cmd/internal/generate_bpf_objects/dummy/dummy.go +++ b/cmd/internal/generate_bpf_objects/dummy/dummy.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package main diff --git a/cmd/internal/generate_bpf_objects/generate_bpf_objects.go b/cmd/internal/generate_bpf_objects/generate_bpf_objects.go index b55281e..5064724 100644 --- a/cmd/internal/generate_bpf_objects/generate_bpf_objects.go +++ b/cmd/internal/generate_bpf_objects/generate_bpf_objects.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package main diff --git a/cmd/validate.go b/cmd/validate.go index ad46e1b..59bc712 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package cmd diff --git a/common/assets/assets.go b/common/assets/assets.go index 255de52..a0c30ef 100644 --- a/common/assets/assets.go +++ b/common/assets/assets.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package assets diff --git a/common/consts/control.go b/common/consts/control.go index b6a7d3e..ed41458 100644 --- a/common/consts/control.go +++ b/common/consts/control.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package consts diff --git a/common/consts/dialer.go b/common/consts/dialer.go index 16e8f4e..2877a0e 100644 --- a/common/consts/dialer.go +++ b/common/consts/dialer.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package consts diff --git a/common/consts/ebpf.go b/common/consts/ebpf.go index b82c364..4e94c9d 100644 --- a/common/consts/ebpf.go +++ b/common/consts/ebpf.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package consts @@ -41,10 +41,10 @@ const ( DisableL4ChecksumPolicy_SetZero ) -type RoutingType uint8 +type MatchType uint8 const ( - MatchType_DomainSet RoutingType = iota + MatchType_DomainSet MatchType = iota MatchType_IpSet MatchType_SourceIpSet MatchType_Port @@ -65,6 +65,7 @@ const ( OutboundControlPlaneDirect OutboundIndex = 0xFD OutboundLogicalOr OutboundIndex = 0xFE OutboundLogicalAnd OutboundIndex = 0xFF + OutboundLogicalMask OutboundIndex = 0xFE OutboundMax = OutboundLogicalAnd OutboundUserDefinedMax = OutboundMustDirect - 1 diff --git a/common/consts/routing.go b/common/consts/routing.go index 99c8673..b11cfc3 100644 --- a/common/consts/routing.go +++ b/common/consts/routing.go @@ -1,15 +1,17 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package consts +type RoutingDomainKey string + const ( - RoutingDomain_Full = "full" - RoutingDomain_Keyword = "keyword" - RoutingDomain_Suffix = "suffix" - RoutingDomain_Regex = "regex" + RoutingDomainKey_Full RoutingDomainKey = "full" + RoutingDomainKey_Keyword RoutingDomainKey = "keyword" + RoutingDomainKey_Suffix RoutingDomainKey = "suffix" + RoutingDomainKey_Regex RoutingDomainKey = "regex" Function_Domain = "domain" Function_Ip = "ip" diff --git a/common/debug.go b/common/debug.go index e33cb60..dac5695 100644 --- a/common/debug.go +++ b/common/debug.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package common diff --git a/common/netutils/context_dialer.go b/common/netutils/context_dialer.go index a1af939..dc71bc6 100644 --- a/common/netutils/context_dialer.go +++ b/common/netutils/context_dialer.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package netutils diff --git a/common/netutils/dns.go b/common/netutils/dns.go index bfe0508..806ebec 100644 --- a/common/netutils/dns.go +++ b/common/netutils/dns.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package netutils diff --git a/common/netutils/ip46.go b/common/netutils/ip46.go index 8f737b1..b8d5512 100644 --- a/common/netutils/ip46.go +++ b/common/netutils/ip46.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package netutils diff --git a/common/netutils/url.go b/common/netutils/url.go index 849252f..93dcaf8 100644 --- a/common/netutils/url.go +++ b/common/netutils/url.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package netutils diff --git a/common/utils.go b/common/utils.go index d191041..69f1fb3 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package common diff --git a/component/outbound/dialer/alive_dialer_set.go b/component/outbound/dialer/alive_dialer_set.go index 260e557..fd08f0e 100644 --- a/component/outbound/dialer/alive_dialer_set.go +++ b/component/outbound/dialer/alive_dialer_set.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package dialer diff --git a/component/outbound/dialer/block.go b/component/outbound/dialer/block.go index 7bdcee3..f2cc014 100644 --- a/component/outbound/dialer/block.go +++ b/component/outbound/dialer/block.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package dialer diff --git a/component/outbound/dialer/connectivity_check.go b/component/outbound/dialer/connectivity_check.go index 223aeeb..aeb4b8a 100644 --- a/component/outbound/dialer/connectivity_check.go +++ b/component/outbound/dialer/connectivity_check.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package dialer diff --git a/component/outbound/dialer/latencies_n.go b/component/outbound/dialer/latencies_n.go index c9b67b3..fd12d74 100644 --- a/component/outbound/dialer/latencies_n.go +++ b/component/outbound/dialer/latencies_n.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package dialer diff --git a/component/outbound/dialer/register.go b/component/outbound/dialer/register.go index 8bf3655..08b8504 100644 --- a/component/outbound/dialer/register.go +++ b/component/outbound/dialer/register.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package dialer diff --git a/component/outbound/dialer/sockopt.go b/component/outbound/dialer/sockopt.go index dda7a1c..4163e0c 100644 --- a/component/outbound/dialer/sockopt.go +++ b/component/outbound/dialer/sockopt.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package dialer diff --git a/component/outbound/dialer_group.go b/component/outbound/dialer_group.go index 1ff0638..d3ed278 100644 --- a/component/outbound/dialer_group.go +++ b/component/outbound/dialer_group.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package outbound diff --git a/component/outbound/dialer_group_test.go b/component/outbound/dialer_group_test.go index 87e7a51..acaf513 100644 --- a/component/outbound/dialer_group_test.go +++ b/component/outbound/dialer_group_test.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package outbound diff --git a/component/outbound/dialer_selection_policy.go b/component/outbound/dialer_selection_policy.go index f35f69c..c169829 100644 --- a/component/outbound/dialer_selection_policy.go +++ b/component/outbound/dialer_selection_policy.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package outbound diff --git a/component/outbound/filter.go b/component/outbound/filter.go index be83788..979157b 100644 --- a/component/outbound/filter.go +++ b/component/outbound/filter.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package outbound diff --git a/component/outbound/outbound.go b/component/outbound/outbound.go index 6a047be..768e50a 100644 --- a/component/outbound/outbound.go +++ b/component/outbound/outbound.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package outbound diff --git a/component/routing/domain_matcher.go b/component/routing/domain_matcher.go new file mode 100644 index 0000000..b615632 --- /dev/null +++ b/component/routing/domain_matcher.go @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2023, v2rayA Organization + */ + +package routing + +import "github.com/v2rayA/dae/common/consts" + +type DomainMatcher interface { + AddSet(bitIndex int, patterns []string, typ consts.RoutingDomainKey) + Build() error + MatchDomainBitmap(domain string) (bitmap []uint32) +} diff --git a/component/routing/domain_matcher/ahocorasick.go b/component/routing/domain_matcher/ahocorasick.go new file mode 100644 index 0000000..a0b7eff --- /dev/null +++ b/component/routing/domain_matcher/ahocorasick.go @@ -0,0 +1,119 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2023, v2rayA Organization + */ + +package domain_matcher + +import ( + "fmt" + "github.com/cloudflare/ahocorasick" + "github.com/v2rayA/dae/common/consts" + "regexp" + "strings" +) + +type Ahocorasick struct { + validIndexes []int + validRegexpIndexes []int + matchers []*ahocorasick.Matcher + regexp [][]*regexp.Regexp + + toBuild [][][]byte + err error +} + +func NewAhocorasick(bitLength int) *Ahocorasick { + return &Ahocorasick{ + matchers: make([]*ahocorasick.Matcher, bitLength), + toBuild: make([][][]byte, bitLength), + regexp: make([][]*regexp.Regexp, bitLength), + } +} +func (n *Ahocorasick) AddSet(bitIndex int, patterns []string, typ consts.RoutingDomainKey) { + if n.err != nil { + return + } + switch typ { + case consts.RoutingDomainKey_Full: + for _, d := range patterns { + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], []byte("^"+d+"$")) + } + case consts.RoutingDomainKey_Suffix: + for _, d := range patterns { + if strings.HasPrefix(d, ".") { + // abc.example.com + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], []byte(d+"$")) + } else { + // xxx.example.com + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], []byte("."+d+"$")) + // example.com + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], []byte("^"+d+"$")) + } + } + case consts.RoutingDomainKey_Keyword: + for _, d := range patterns { + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], []byte(d)) + } + case consts.RoutingDomainKey_Regex: + for _, d := range patterns { + r, err := regexp.Compile(d) + if err != nil { + n.err = fmt.Errorf("failed to compile regex: %v", d) + return + } + n.regexp[bitIndex] = append(n.regexp[bitIndex], r) + } + default: + n.err = fmt.Errorf("unknown RoutingDomainKey: %v", typ) + return + } +} +func (n *Ahocorasick) MatchDomainBitmap(domain string) (bitmap []uint32) { + N := len(n.matchers) / 32 + if len(n.matchers)%32 != 0 { + N++ + } + bitmap = make([]uint32, N) + // Domain should not contain ^ or $. + if strings.ContainsAny(domain, "^$") { + return bitmap + } + // Add magic chars as head and tail. + domain = "^" + strings.ToLower(strings.TrimSuffix(domain, ".")) + "$" + for _, i := range n.validIndexes { + if hits := n.matchers[i].MatchThreadSafe([]byte(domain)); len(hits) > 0 { + bitmap[i/32] |= 1 << (i % 32) + } + } + // Regex matching is independent. + for _, i := range n.validRegexpIndexes { + if bitmap[i/32]&(1<<(i%32)) > 0 { + // Already matched. + continue + } + for _, r := range n.regexp[i] { + if r.MatchString(domain) { + bitmap[i/32] |= 1 << (i % 32) + break + } + } + } + return bitmap +} +func (n *Ahocorasick) Build() error { + if n.err != nil { + return n.err + } + n.validIndexes = make([]int, 0, len(n.toBuild)/8) + for i, toBuild := range n.toBuild { + if len(toBuild) == 0 { + continue + } + n.matchers[i] = ahocorasick.NewMatcher(toBuild) + n.validIndexes = append(n.validIndexes, i) + } + // Release it. + n.toBuild = nil + return nil +} diff --git a/component/routing/domain_matcher/benchmark_test.go b/component/routing/domain_matcher/benchmark_test.go new file mode 100644 index 0000000..969a9c5 --- /dev/null +++ b/component/routing/domain_matcher/benchmark_test.go @@ -0,0 +1,236 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2023, v2rayA Organization + */ + +package domain_matcher + +import ( + "fmt" + "github.com/sirupsen/logrus" + "github.com/v2rayA/dae/common/consts" + "github.com/v2rayA/dae/component/routing" + "github.com/v2rayA/dae/config" + "github.com/v2rayA/dae/pkg/config_parser" + "hash/fnv" + "math/rand" + "reflect" + "testing" +) + +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", +} + +type RoutingMatcherBuilder struct { + *routing.DefaultMatcherBuilder + outboundName2Id map[string]uint8 + 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 string) { + 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 { + pname(NetworkManager, dnsmasq, systemd-resolved) -> must_direct # Traffic of DNS in local must be direct to avoid loop when binding to WAN. + pname(sogou-qimpanel, sogou-qimpanel-watchdog) -> block + ip(geoip:private, 224.0.0.0/3, 'ff00::/8') -> direct # Put it in front unless you know what you're doing. + domain(geosite:bing)->us + domain(full:dns.google) && port(53) -> direct + domain(geosite:category-ads-all) -> block + ip(geoip:private) -> direct + ip(geoip:cn) -> direct + domain(geosite:cn) -> direct + final: my_group +}`) + if err != nil { + return nil, err + } + var r config.Routing + if err = config.RoutingRuleAndParamParser(reflect.ValueOf(&r), sections[0]); err != nil { + return nil, err + } + if rules, err = routing.ApplyRulesOptimizers(r.Rules, + &routing.RefineFunctionParamKeyOptimizer{}, + &routing.DatReaderOptimizer{Logger: logrus.StandardLogger()}, + &routing.MergeAndSortRulesOptimizer{}, + &routing.DeduplicateParamsOptimizer{}, + ); err != nil { + return nil, fmt.Errorf("ApplyRulesOptimizers error:\n%w", err) + } + builder := RoutingMatcherBuilder{} + if err = routing.ApplyMatcherBuilder(logrus.StandardLogger(), &builder, rules, r.Fallback); err != nil { + return nil, fmt.Errorf("ApplyMatcherBuilder: %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(simulatedDomainSet) + 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 BenchmarkAhocorasick(b *testing.B) { + b.StopTimer() + logrus.SetLevel(logrus.WarnLevel) + simulatedDomainSet, err := getDomain() + if err != nil { + b.Fatal(err) + } + ahocorasick := NewAhocorasick(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) + } +} diff --git a/component/routing/domain_matcher/bruteforce.go b/component/routing/domain_matcher/bruteforce.go new file mode 100644 index 0000000..ac86aea --- /dev/null +++ b/component/routing/domain_matcher/bruteforce.go @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2023, v2rayA Organization + */ + +package domain_matcher + +import ( + "github.com/v2rayA/dae/common/consts" + "github.com/v2rayA/dae/component/routing" + "regexp" + "strings" +) + +type Bruteforce struct { + simulatedDomainSet []routing.DomainSet + err error +} + +func NewBruteforce(simulatedDomainSet []routing.DomainSet) *Bruteforce { + return &Bruteforce{ + simulatedDomainSet: simulatedDomainSet, + } +} +func (n *Bruteforce) AddSet(bitIndex int, patterns []string, typ consts.RoutingDomainKey) { +} +func (n *Bruteforce) MatchDomainBitmap(domain string) (bitmap []uint32) { + N := len(n.simulatedDomainSet) / 32 + if len(n.simulatedDomainSet)%32 != 0 { + N++ + } + bitmap = make([]uint32, N) + for _, s := range n.simulatedDomainSet { + for _, d := range s.Domains { + var hit bool + switch s.Key { + case consts.RoutingDomainKey_Suffix: + if domain == d || strings.HasSuffix(domain, "."+strings.TrimPrefix(d, ".")) { + hit = true + } + case consts.RoutingDomainKey_Full: + if strings.EqualFold(domain, d) { + hit = true + } + case consts.RoutingDomainKey_Keyword: + if strings.Contains(strings.ToLower(domain), strings.ToLower(d)) { + hit = true + } + case consts.RoutingDomainKey_Regex: + if regexp.MustCompile(d).MatchString(strings.ToLower(domain)) { + hit = true + } + } + if hit { + bitmap[s.RuleIndex/32] |= 1 << (s.RuleIndex % 32) + break + } + } + } + return bitmap +} +func (n *Bruteforce) Build() error { + if n.err != nil { + return n.err + } + return nil +} diff --git a/component/routing/domain_matcher/go_regexp_nfa.go b/component/routing/domain_matcher/go_regexp_nfa.go new file mode 100644 index 0000000..8f693a0 --- /dev/null +++ b/component/routing/domain_matcher/go_regexp_nfa.go @@ -0,0 +1,93 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2023, v2rayA Organization + */ + +package domain_matcher + +import ( + "fmt" + "github.com/v2rayA/dae/common/consts" + "regexp" + "strings" +) + +type GoRegexpNfa struct { + validIndexes []int + nfa []*regexp.Regexp + toBuild [][]string + err error +} + +func NewGoRegexpNfa(bitLength int) *GoRegexpNfa { + return &GoRegexpNfa{ + nfa: make([]*regexp.Regexp, bitLength), + toBuild: make([][]string, bitLength), + } +} +func (n *GoRegexpNfa) AddSet(bitIndex int, patterns []string, typ consts.RoutingDomainKey) { + if n.err != nil { + return + } + switch typ { + case consts.RoutingDomainKey_Full: + for _, d := range patterns { + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], "^"+d+"$") + } + case consts.RoutingDomainKey_Suffix: + for _, d := range patterns { + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], "."+strings.TrimPrefix(d, ".")+"$") + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], "^"+d+"$") + } + case consts.RoutingDomainKey_Keyword: + for _, d := range patterns { + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], d) + } + case consts.RoutingDomainKey_Regex: + for _, d := range patterns { + // Check if it is a valid regexp. + if _, err := regexp.Compile(d); err != nil { + n.err = fmt.Errorf("failed to compile regex: %v", d) + return + } + n.toBuild[bitIndex] = append(n.toBuild[bitIndex], d) + } + default: + n.err = fmt.Errorf("unknown RoutingDomainKey: %v", typ) + return + } +} +func (n *GoRegexpNfa) MatchDomainBitmap(domain string) (bitmap []uint32) { + N := len(n.nfa) / 32 + if len(n.nfa)%32 != 0 { + N++ + } + bitmap = make([]uint32, N) + domain = strings.ToLower(strings.TrimSuffix(domain, ".")) + for _, i := range n.validIndexes { + if n.nfa[i].MatchString(domain) { + bitmap[i/32] |= 1 << (i % 32) + } + } + return bitmap +} +func (n *GoRegexpNfa) Build() error { + if n.err != nil { + return n.err + } + n.validIndexes = make([]int, 0, len(n.toBuild)/8) + for i, toBuild := range n.toBuild { + if len(toBuild) == 0 { + continue + } + r, err := regexp.Compile(strings.Join(toBuild, "|")) + if err != nil { + return fmt.Errorf("failed to build NFA: %w", err) + } + n.nfa[i] = r + n.validIndexes = append(n.validIndexes, i) + } + // Release it. + n.toBuild = nil + return nil +} diff --git a/component/routing/matcher_builder.go b/component/routing/matcher_builder.go index 278d03e..cacd199 100644 --- a/component/routing/matcher_builder.go +++ b/component/routing/matcher_builder.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package routing @@ -19,6 +19,12 @@ var FakeOutbound_MUST_DIRECT = consts.OutboundMustDirect.String() var FakeOutbound_AND = consts.OutboundLogicalAnd.String() var FakeOutbound_OR = consts.OutboundLogicalOr.String() +type DomainSet struct { + Key consts.RoutingDomainKey + RuleIndex int + Domains []string +} + type MatcherBuilder interface { AddDomain(f *config_parser.Function, key string, values []string, outbound string) AddIp(f *config_parser.Function, values []netip.Prefix, outbound string) @@ -32,7 +38,6 @@ type MatcherBuilder interface { AddFallback(outbound string) AddAnyBefore(f *config_parser.Function, key string, values []string, outbound string) AddAnyAfter(f *config_parser.Function, key string, values []string, outbound string) - Build() (err error) } func GroupParamValuesByKey(params []*config_parser.Param) (keyToValues map[string][]string, keyOrder []string) { @@ -207,4 +212,3 @@ func (d *DefaultMatcherBuilder) AddProcessName(f *config_parser.Function, values } func (d *DefaultMatcherBuilder) AddAnyAfter(f *config_parser.Function, key string, values []string, outbound string) { } -func (d *DefaultMatcherBuilder) Build() (err error) { return nil } diff --git a/component/routing/optimizer.go b/component/routing/optimizer.go index 190d252..90ef1fd 100644 --- a/component/routing/optimizer.go +++ b/component/routing/optimizer.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package routing @@ -49,9 +49,9 @@ func (o *RefineFunctionParamKeyOptimizer) Optimize(rules []*config_parser.Routin // Rewrite to authoritative key name. switch param.Key { case "", "domain": - param.Key = consts.RoutingDomain_Suffix + param.Key = string(consts.RoutingDomainKey_Suffix) case "contains": - param.Key = consts.RoutingDomain_Keyword + param.Key = string(consts.RoutingDomainKey_Keyword) default: } } @@ -169,25 +169,25 @@ func (o *DatReaderOptimizer) loadGeoSite(filename string, code string) (params [ case geodata.Domain_Full: // Full. params = append(params, &config_parser.Param{ - Key: consts.RoutingDomain_Full, + Key: string(consts.RoutingDomainKey_Full), Val: item.Value, }) case geodata.Domain_RootDomain: // Suffix. params = append(params, &config_parser.Param{ - Key: consts.RoutingDomain_Suffix, + Key: string(consts.RoutingDomainKey_Suffix), Val: item.Value, }) case geodata.Domain_Plain: // Keyword. params = append(params, &config_parser.Param{ - Key: consts.RoutingDomain_Keyword, + Key: string(consts.RoutingDomainKey_Keyword), Val: item.Value, }) case geodata.Domain_Regex: // Regex. params = append(params, &config_parser.Param{ - Key: consts.RoutingDomain_Regex, + Key: string(consts.RoutingDomainKey_Regex), Val: item.Value, }) } diff --git a/component/sniffing/conn_sniffer.go b/component/sniffing/conn_sniffer.go index 075b37a..a5c6eb6 100644 --- a/component/sniffing/conn_sniffer.go +++ b/component/sniffing/conn_sniffer.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/component/sniffing/http.go b/component/sniffing/http.go index 88ee2df..93d0f80 100644 --- a/component/sniffing/http.go +++ b/component/sniffing/http.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/component/sniffing/internal/quicutils/binary.go b/component/sniffing/internal/quicutils/binary.go index 56afa13..3f8fa19 100644 --- a/component/sniffing/internal/quicutils/binary.go +++ b/component/sniffing/internal/quicutils/binary.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package quicutils diff --git a/component/sniffing/internal/quicutils/cipher.go b/component/sniffing/internal/quicutils/cipher.go index cabfb30..7ed9255 100644 --- a/component/sniffing/internal/quicutils/cipher.go +++ b/component/sniffing/internal/quicutils/cipher.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package quicutils diff --git a/component/sniffing/internal/quicutils/cipher_test.go b/component/sniffing/internal/quicutils/cipher_test.go index 29a8d16..791ce7a 100644 --- a/component/sniffing/internal/quicutils/cipher_test.go +++ b/component/sniffing/internal/quicutils/cipher_test.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package quicutils diff --git a/component/sniffing/internal/quicutils/relocation.go b/component/sniffing/internal/quicutils/relocation.go index d474d95..5619a87 100644 --- a/component/sniffing/internal/quicutils/relocation.go +++ b/component/sniffing/internal/quicutils/relocation.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package quicutils diff --git a/component/sniffing/internal/quicutils/version.go b/component/sniffing/internal/quicutils/version.go index ddcb809..617ae9a 100644 --- a/component/sniffing/internal/quicutils/version.go +++ b/component/sniffing/internal/quicutils/version.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package quicutils diff --git a/component/sniffing/quic.go b/component/sniffing/quic.go index a96851b..7fed7d0 100644 --- a/component/sniffing/quic.go +++ b/component/sniffing/quic.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/component/sniffing/quic_bench_test.go b/component/sniffing/quic_bench_test.go index 1b11988..e1eaa3c 100644 --- a/component/sniffing/quic_bench_test.go +++ b/component/sniffing/quic_bench_test.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/component/sniffing/sniffer.go b/component/sniffing/sniffer.go index 0e292c9..9a580b1 100644 --- a/component/sniffing/sniffer.go +++ b/component/sniffing/sniffer.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/component/sniffing/sniffing.go b/component/sniffing/sniffing.go index bbeff11..e824312 100644 --- a/component/sniffing/sniffing.go +++ b/component/sniffing/sniffing.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/component/sniffing/sniffing_bench_test.go b/component/sniffing/sniffing_bench_test.go index ccc0b1d..53f6bc3 100644 --- a/component/sniffing/sniffing_bench_test.go +++ b/component/sniffing/sniffing_bench_test.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/component/sniffing/tls.go b/component/sniffing/tls.go index b7e9991..1d363a7 100644 --- a/component/sniffing/tls.go +++ b/component/sniffing/tls.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/component/sniffing/tls_test.go b/component/sniffing/tls_test.go index 3531c2c..480ccc6 100644 --- a/component/sniffing/tls_test.go +++ b/component/sniffing/tls_test.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package sniffing diff --git a/config/config.go b/config/config.go index 212d997..901f68d 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package config diff --git a/config/config_merger.go b/config/config_merger.go index 748de50..7d53445 100644 --- a/config/config_merger.go +++ b/config/config_merger.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package config diff --git a/config/parser.go b/config/parser.go index 366ed8f..9c30750 100644 --- a/config/parser.go +++ b/config/parser.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package config diff --git a/config/patch.go b/config/patch.go index b6c3bac..adb7c9f 100644 --- a/config/patch.go +++ b/config/patch.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package config diff --git a/control/addr.go b/control/addr.go index aeb8d24..206551f 100644 --- a/control/addr.go +++ b/control/addr.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/bpf_subobjects.go b/control/bpf_subobjects.go index 596f0a2..b0f79fb 100644 --- a/control/bpf_subobjects.go +++ b/control/bpf_subobjects.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/bpf_utils.go b/control/bpf_utils.go index a07e6c4..91336c3 100644 --- a/control/bpf_utils.go +++ b/control/bpf_utils.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package control @@ -48,6 +48,12 @@ func (r _bpfPortRange) Encode() (b [16]byte) { return b } +func ParsePortRange(b []byte) (portStart, portEnd uint16) { + portStart = binary.LittleEndian.Uint16(b[:2]) + portEnd = binary.LittleEndian.Uint16(b[2:]) + return portStart, portEnd +} + func (o *bpfObjects) newLpmMap(keys []_bpfLpmKey, values []uint32) (m *ebpf.Map, err error) { m, err = ebpf.NewMap(&ebpf.MapSpec{ Type: ebpf.LPMTrie, diff --git a/control/connectivity.go b/control/connectivity.go index efd9e15..5a8e5a0 100644 --- a/control/connectivity.go +++ b/control/connectivity.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/control.go b/control/control.go index ce81cbc..bf631cf 100644 --- a/control/control.go +++ b/control/control.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/control_plane.go b/control/control_plane.go index 744885d..392162b 100644 --- a/control/control_plane.go +++ b/control/control_plane.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package control @@ -43,16 +43,14 @@ type ControlPlane struct { // TODO: add mutex? outbounds []*outbound.DialerGroup - SimulatedLpmTries [][]netip.Prefix - SimulatedDomainSet []DomainSet - Fallback string - // mutex protects the dnsCache. dnsCacheMu sync.Mutex dnsCache map[string]*dnsCache dnsUpstream DnsUpstreamRaw dialMode consts.DialMode + + routingMatcher *RoutingMatcher } func NewControlPlane( @@ -294,28 +292,30 @@ func NewControlPlane( if err = routing.ApplyMatcherBuilder(log, builder, rules, routingA.Fallback); err != nil { return nil, fmt.Errorf("ApplyMatcherBuilder: %w", err) } - if err = builder.Build(); err != nil { - return nil, fmt.Errorf("RoutingMatcherBuilder.Build: %w", err) + if err = builder.BuildKernspace(); err != nil { + return nil, fmt.Errorf("RoutingMatcherBuilder.BuildKernspace: %w", err) + } + routingMatcher, err := builder.BuildUserspace() + if err != nil { + return nil, fmt.Errorf("RoutingMatcherBuilder.BuildKernspace: %w", err) } dialMode, err := consts.ParseDialMode(global.DialMode) c = &ControlPlane{ - log: log, - core: core, - deferFuncs: nil, - listenIp: "0.0.0.0", - outbounds: outbounds, - SimulatedLpmTries: builder.SimulatedLpmTries, - SimulatedDomainSet: builder.SimulatedDomainSet, - Fallback: routingA.Fallback, - dnsCacheMu: sync.Mutex{}, - dnsCache: make(map[string]*dnsCache), + log: log, + core: core, + deferFuncs: nil, + listenIp: "0.0.0.0", + outbounds: outbounds, + dnsCacheMu: sync.Mutex{}, + dnsCache: make(map[string]*dnsCache), dnsUpstream: DnsUpstreamRaw{ Raw: global.DnsUpstream, FinishInitCallback: nil, }, - dialMode: dialMode, + dialMode: dialMode, + routingMatcher: routingMatcher, } /// DNS upstream diff --git a/control/control_plane_core.go b/control/control_plane_core.go index afc93f9..1cc8ddc 100644 --- a/control/control_plane_core.go +++ b/control/control_plane_core.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/dns.go b/control/dns.go index d3a4736..0b279f8 100644 --- a/control/dns.go +++ b/control/dns.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package control @@ -13,7 +13,6 @@ import ( "github.com/mohae/deepcopy" "github.com/sirupsen/logrus" "github.com/v2rayA/dae/common" - "github.com/v2rayA/dae/common/consts" "golang.org/x/net/dns/dnsmessage" "hash/fnv" "math/rand" @@ -28,7 +27,7 @@ var ( ) type dnsCache struct { - DomainBitmap [consts.MaxMatchSetLen / 32]uint32 + DomainBitmap []uint32 Answers []dnsmessage.Resource Deadline time.Time } @@ -95,8 +94,12 @@ func (c *ControlPlane) BatchUpdateDomainRouting(cache *dnsCache) error { ip6 := ip.As16() keys = append(keys, common.Ipv6ByteSliceToUint32Array(ip6[:])) vals = append(vals, bpfDomainRouting{ - Bitmap: cache.DomainBitmap, + Bitmap: [3]uint32{}, }) + if len(cache.DomainBitmap) != len(vals[len(vals)-1].Bitmap) { + return fmt.Errorf("domain bitmap length not sync with kern program") + } + copy(vals[len(vals)-1].Bitmap[:], cache.DomainBitmap) } if _, err := BpfMapBatchUpdate(c.core.bpf.DomainRoutingMap, keys, vals, &ebpf.BatchOptions{ ElemFlags: uint64(ebpf.UpdateAny), @@ -341,7 +344,7 @@ func (c *ControlPlane) UpdateDnsCache(host string, typ dnsmessage.Type, answers cache.Answers = answers } else { cache = &dnsCache{ - DomainBitmap: c.MatchDomainBitmap(strings.TrimSuffix(fqdn, ".")), + DomainBitmap: c.routingMatcher.domainMatcher.MatchDomainBitmap(fqdn), Answers: answers, Deadline: deadline, } diff --git a/control/dns_upstream.go b/control/dns_upstream.go index 1731536..fba73e7 100644 --- a/control/dns_upstream.go +++ b/control/dns_upstream.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/domain_match.go b/control/domain_match.go deleted file mode 100644 index 6512126..0000000 --- a/control/domain_match.go +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization - */ - -package control - -import ( - "github.com/v2rayA/dae/common/consts" - "regexp" - "strings" -) - -func (c *ControlPlane) MatchDomainBitmap(domain string) (bitmap [consts.MaxMatchSetLen / 32]uint32) { - // TODO: high performance implementation. - for _, s := range c.SimulatedDomainSet { - for _, d := range s.Domains { - var hit bool - switch s.Key { - case consts.RoutingDomain_Suffix: - if domain == d || strings.HasSuffix(domain, "."+d) { - hit = true - } - case consts.RoutingDomain_Full: - if strings.EqualFold(domain, d) { - hit = true - } - case consts.RoutingDomain_Keyword: - if strings.Contains(strings.ToLower(domain), strings.ToLower(d)) { - hit = true - } - case consts.RoutingDomain_Regex: - // FIXME: too slow - for _, d := range s.Domains { - if regexp.MustCompile(d).MatchString(strings.ToLower(domain)) { - hit = true - break - } - } - } - if hit { - bitmap[s.RuleIndex/32] |= 1 << (s.RuleIndex % 32) - break - } - } - } - return bitmap -} diff --git a/control/kern/tproxy.c b/control/kern/tproxy.c index 7f69e95..5ac698e 100644 --- a/control/kern/tproxy.c +++ b/control/kern/tproxy.c @@ -1,7 +1,7 @@ // +build ignore /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ #include diff --git a/control/routing_matcher_builder.go b/control/routing_matcher_builder.go index 84e881c..81b99dd 100644 --- a/control/routing_matcher_builder.go +++ b/control/routing_matcher_builder.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package control @@ -8,28 +8,24 @@ package control import ( "encoding/binary" "fmt" + "github.com/Asphaltt/lpmtrie" "github.com/cilium/ebpf" "github.com/v2rayA/dae/common" "github.com/v2rayA/dae/common/consts" "github.com/v2rayA/dae/component/routing" + "github.com/v2rayA/dae/component/routing/domain_matcher" "github.com/v2rayA/dae/pkg/config_parser" "net/netip" "strconv" ) -type DomainSet struct { - Key string - RuleIndex int - Domains []string -} - type RoutingMatcherBuilder struct { *routing.DefaultMatcherBuilder outboundName2Id map[string]uint8 bpf *bpfObjects rules []bpfMatchSet - SimulatedLpmTries [][]netip.Prefix - SimulatedDomainSet []DomainSet + simulatedLpmTries [][]netip.Prefix + simulatedDomainSet []routing.DomainSet Fallback string err error @@ -62,17 +58,17 @@ func (b *RoutingMatcherBuilder) AddDomain(f *config_parser.Function, key string, if b.err != nil { return } - switch key { - case consts.RoutingDomain_Regex, - consts.RoutingDomain_Full, - consts.RoutingDomain_Keyword, - consts.RoutingDomain_Suffix: + 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, DomainSet{ - Key: key, + b.simulatedDomainSet = append(b.simulatedDomainSet, routing.DomainSet{ + Key: consts.RoutingDomainKey(key), RuleIndex: len(b.rules), Domains: values, }) @@ -94,8 +90,8 @@ func (b *RoutingMatcherBuilder) AddSourceMac(f *config_parser.Function, macAddrs prefix := netip.PrefixFrom(netip.AddrFrom16(addr16), 128) values = append(values, prefix) } - lpmTrieIndex := len(b.SimulatedLpmTries) - b.SimulatedLpmTries = append(b.SimulatedLpmTries, values) + lpmTrieIndex := len(b.simulatedLpmTries) + b.simulatedLpmTries = append(b.simulatedLpmTries, values) set := bpfMatchSet{ Value: [16]byte{}, Type: uint8(consts.MatchType_Mac), @@ -111,8 +107,8 @@ func (b *RoutingMatcherBuilder) AddIp(f *config_parser.Function, values []netip. if b.err != nil { return } - lpmTrieIndex := len(b.SimulatedLpmTries) - b.SimulatedLpmTries = append(b.SimulatedLpmTries, values) + lpmTrieIndex := len(b.simulatedLpmTries) + b.simulatedLpmTries = append(b.simulatedLpmTries, values) set := bpfMatchSet{ Value: [16]byte{}, Type: uint8(consts.MatchType_IpSet), @@ -145,8 +141,8 @@ func (b *RoutingMatcherBuilder) AddSourceIp(f *config_parser.Function, values [] if b.err != nil { return } - lpmTrieIndex := len(b.SimulatedLpmTries) - b.SimulatedLpmTries = append(b.SimulatedLpmTries, values) + lpmTrieIndex := len(b.simulatedLpmTries) + b.simulatedLpmTries = append(b.simulatedLpmTries, values) set := bpfMatchSet{ Value: [16]byte{}, Type: uint8(consts.MatchType_SourceIpSet), @@ -226,12 +222,12 @@ func (b *RoutingMatcherBuilder) AddFallback(outbound string) { }) } -func (b *RoutingMatcherBuilder) Build() (err error) { +func (b *RoutingMatcherBuilder) BuildKernspace() (err error) { if b.err != nil { return b.err } // Update lpm_array_map. - for i, cidrs := range b.SimulatedLpmTries { + for i, cidrs := range b.simulatedLpmTries { var keys []_bpfLpmKey var values []uint32 for _, cidr := range cidrs { @@ -264,3 +260,40 @@ func (b *RoutingMatcherBuilder) Build() (err error) { } return nil } + +func (b *RoutingMatcherBuilder) BuildUserspace() (matcher *RoutingMatcher, err error) { + if b.err != nil { + return nil, b.err + } + var m RoutingMatcher + // Update lpms. + m.lpms = make([]lpmtrie.LpmTrie, len(b.simulatedLpmTries)) + for i, cidrs := range b.simulatedLpmTries { + lpm, err := lpmtrie.New(128) + if err != nil { + return nil, err + } + for _, cidr := range cidrs { + lpm.Update(cidrToLpmTrieKey(cidr), 1) + } + m.lpms[i] = lpm + } + // Build domainMatcher + m.domainMatcher = domain_matcher.NewAhocorasick(consts.MaxMatchSetLen) + for _, domains := range b.simulatedDomainSet { + m.domainMatcher.AddSet(domains.RuleIndex, domains.Domains, domains.Key) + } + if err = m.domainMatcher.Build(); err != nil { + return nil, err + } + + // Write routings. + // Fallback rule MUST be the last. + if b.rules[len(b.rules)-1].Type != uint8(consts.MatchType_Fallback) { + b.err = fmt.Errorf("fallback rule MUST be the last") + return nil, b.err + } + m.matches = b.rules + + return &m, nil +} diff --git a/control/routing_matcher_userspace.go b/control/routing_matcher_userspace.go new file mode 100644 index 0000000..58c875a --- /dev/null +++ b/control/routing_matcher_userspace.go @@ -0,0 +1,150 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2023, v2rayA Organization + */ + +package control + +import ( + "encoding/binary" + "fmt" + "github.com/Asphaltt/lpmtrie" + "github.com/v2rayA/dae/common/consts" + "github.com/v2rayA/dae/component/routing" + "net" + "net/netip" +) + +type RoutingMatcher struct { + lpms []lpmtrie.LpmTrie + domainMatcher routing.DomainMatcher // All domain matchSets use one DomainMatcher. + + matches []bpfMatchSet +} + +// Match is modified from kern/tproxy.c; please keep sync. +func (m *RoutingMatcher) Match( + sourceAddr []byte, + destAddr []byte, + sourcePort uint16, + destPort uint16, + ipVersion consts.IpVersionType, + l4proto consts.L4ProtoType, + domain string, + processName string, + mac []byte, +) (outboundIndex consts.OutboundIndex, err error) { + if len(sourceAddr) != net.IPv6len || len(destAddr) != net.IPv6len || len(mac) != net.IPv6len { + return 0, fmt.Errorf("bad address length") + } + lpmKeys := make([]*lpmtrie.Key, consts.MatchType_Mac+1) + lpmKeys[consts.MatchType_IpSet] = &lpmtrie.Key{ + PrefixLen: 128, + Data: destAddr, + } + lpmKeys[consts.MatchType_SourceIpSet] = &lpmtrie.Key{ + PrefixLen: 128, + Data: sourceAddr, + } + lpmKeys[consts.MatchType_Mac] = &lpmtrie.Key{ + PrefixLen: 128, + Data: mac, + } + var domainMatchBitmap []uint32 + if domain != "" { + domainMatchBitmap = m.domainMatcher.MatchDomainBitmap(domain) + } + + goodSubrule := false + badRule := false + for i, match := range m.matches { + if badRule || goodSubrule { + goto beforeNextLoop + } + switch consts.MatchType(match.Type) { + case consts.MatchType_IpSet, consts.MatchType_SourceIpSet, consts.MatchType_Mac: + lpmIndex := int(binary.LittleEndian.Uint16(match.Value[:])) + _, hit := m.lpms[lpmIndex].Lookup(*lpmKeys[int(match.Type)]) + if hit { + goodSubrule = true + } + case consts.MatchType_DomainSet: + if domainMatchBitmap != nil && (domainMatchBitmap[i/32]>>(i%32))&1 > 0 { + goodSubrule = true + } + case consts.MatchType_Port: + portStart, portEnd := ParsePortRange(match.Value[:]) + if destPort >= portStart && + destPort <= portEnd { + goodSubrule = true + } + case consts.MatchType_SourcePort: + portStart, portEnd := ParsePortRange(match.Value[:]) + if sourcePort >= portStart && + sourcePort <= portEnd { + goodSubrule = true + } + case consts.MatchType_IpVersion: + // LittleEndian + if ipVersion&consts.IpVersionType(match.Value[0]) > 0 { + goodSubrule = true + } + case consts.MatchType_L4Proto: + // LittleEndian + if l4proto&consts.L4ProtoType(match.Value[0]) > 0 { + goodSubrule = true + } + case consts.MatchType_ProcessName: + if processName != "" && string(match.Value[:]) == processName { + goodSubrule = true + } + case consts.MatchType_Fallback: + goodSubrule = true + default: + return 0, fmt.Errorf("unknown match type: %v", match.Type) + } + beforeNextLoop: + outbound := consts.OutboundIndex(match.Outbound) + if outbound != consts.OutboundLogicalOr { + // This match_set reaches the end of subrule. + // We are now at end of rule, or next match_set belongs to another + // subrule. + + if goodSubrule == match.Not { + // This subrule does not hit. + badRule = true + } + + // Reset goodSubrule. + goodSubrule = false + } + + if outbound&consts.OutboundLogicalMask != + consts.OutboundLogicalMask { + // Tail of a rule (line). + // Decide whether to hit. + if !badRule { + if outbound == consts.OutboundDirect && destPort == 53 && + l4proto == consts.L4ProtoType_UDP { + // DNS packet should go through control plane. + return consts.OutboundControlPlaneDirect, nil + } + return outbound, nil + } + badRule = false + } + } + return 0, fmt.Errorf("no match set hit") +} + +func cidrToLpmTrieKey(prefix netip.Prefix) lpmtrie.Key { + bits := prefix.Bits() + if prefix.Addr().Is4() { + bits += 96 + } + ip := prefix.Addr().As16() + return lpmtrie.Key{ + PrefixLen: bits, + Data: ip[:], + } +} diff --git a/control/tcp.go b/control/tcp.go index 5056d6e..6fd4ddd 100644 --- a/control/tcp.go +++ b/control/tcp.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/tproxy_utils.go b/control/tproxy_utils.go index 7e8ab23..e691c56 100644 --- a/control/tproxy_utils.go +++ b/control/tproxy_utils.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, mzz2017 + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/udp.go b/control/udp.go index 1b70317..3d53b03 100644 --- a/control/udp.go +++ b/control/udp.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/control/udp_endpoint.go b/control/udp_endpoint.go index 98f189a..127c1e2 100644 --- a/control/udp_endpoint.go +++ b/control/udp_endpoint.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package control diff --git a/go.mod b/go.mod index 15e8d61..2f9e94e 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module github.com/v2rayA/dae go 1.18 require ( + github.com/Asphaltt/lpmtrie v0.0.0-20220205153150-3d814250b8ab github.com/adrg/xdg v0.4.0 github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 github.com/cilium/ebpf v0.10.0 + github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184 github.com/gorilla/websocket v1.5.0 github.com/json-iterator/go v1.1.12 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 diff --git a/go.sum b/go.sum index d12e1c0..782dd0e 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ +github.com/Asphaltt/lpmtrie v0.0.0-20220205153150-3d814250b8ab h1:hzN25CB5VzeKk3/c1fi1oT03N+5365nVOMPAxixkADY= +github.com/Asphaltt/lpmtrie v0.0.0-20220205153150-3d814250b8ab/go.mod h1:TdNTLzn3VVXKfmHAULK5gY+h/A1gLQ8NnwLB6cSN54g= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ= github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE= +github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184 h1:8yL+85JpbwrIc6m+7N1iYrjn/22z68jwrTIBOJHNe4k= +github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184/go.mod h1:tGWUZLZp9ajsxUOnHmFFLnqnlKXsCn6GReG4jAD59H0= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/main.go b/main.go index f351888..c5abdfb 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,7 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package main diff --git a/pkg/config_parser/config_parser.go b/pkg/config_parser/config_parser.go index 787f5ce..61d7a73 100644 --- a/pkg/config_parser/config_parser.go +++ b/pkg/config_parser/config_parser.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package config_parser diff --git a/pkg/config_parser/config_parser_test.go b/pkg/config_parser/config_parser_test.go index 5cd838e..d7a886b 100644 --- a/pkg/config_parser/config_parser_test.go +++ b/pkg/config_parser/config_parser_test.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package config_parser diff --git a/pkg/config_parser/error.go b/pkg/config_parser/error.go index 6ab7f82..d52efd8 100644 --- a/pkg/config_parser/error.go +++ b/pkg/config_parser/error.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package config_parser diff --git a/pkg/config_parser/section.go b/pkg/config_parser/section.go index 1a9f59d..85c6e2a 100644 --- a/pkg/config_parser/section.go +++ b/pkg/config_parser/section.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package config_parser diff --git a/pkg/config_parser/walker.go b/pkg/config_parser/walker.go index 320a44c..af58239 100644 --- a/pkg/config_parser/walker.go +++ b/pkg/config_parser/walker.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2022, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package config_parser diff --git a/pkg/geodata/decode.go b/pkg/geodata/decode.go index a4a2d58..5a96d2a 100644 --- a/pkg/geodata/decode.go +++ b/pkg/geodata/decode.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ // Modified from https://github.com/v2fly/v2ray-core/blob/42b166760b2ba8d984e514b830fcd44e23728e43/infra/conf/geodata/memconservative diff --git a/pkg/geodata/geodata.go b/pkg/geodata/geodata.go index b81e408..cb77cba 100644 --- a/pkg/geodata/geodata.go +++ b/pkg/geodata/geodata.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ // Modified from https://github.com/v2fly/v2ray-core/blob/42b166760b2ba8d984e514b830fcd44e23728e43/infra/conf/geodata/memconservative diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index cc10191..10d6517 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) since 2023, v2rayA Organization + * Copyright (c) 2022-2023, v2rayA Organization */ package logger