feat/optimize: add userspace routing and optimize domain routing (#18)

This commit is contained in:
mzz 2023-02-18 18:27:28 +08:00 committed by GitHub
parent 87efa3d38d
commit 8f6b0a6e2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 862 additions and 176 deletions

View File

@ -1,6 +1,6 @@
# #
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
# Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> # Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
# #
# The development version of clang is distributed as the 'clang' binary, # The development version of clang is distributed as the 'clang' binary,

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package main package main

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package main package main

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package cmd package cmd

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package assets package assets

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package consts package consts

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package consts package consts

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package consts package consts
@ -41,10 +41,10 @@ const (
DisableL4ChecksumPolicy_SetZero DisableL4ChecksumPolicy_SetZero
) )
type RoutingType uint8 type MatchType uint8
const ( const (
MatchType_DomainSet RoutingType = iota MatchType_DomainSet MatchType = iota
MatchType_IpSet MatchType_IpSet
MatchType_SourceIpSet MatchType_SourceIpSet
MatchType_Port MatchType_Port
@ -65,6 +65,7 @@ const (
OutboundControlPlaneDirect OutboundIndex = 0xFD OutboundControlPlaneDirect OutboundIndex = 0xFD
OutboundLogicalOr OutboundIndex = 0xFE OutboundLogicalOr OutboundIndex = 0xFE
OutboundLogicalAnd OutboundIndex = 0xFF OutboundLogicalAnd OutboundIndex = 0xFF
OutboundLogicalMask OutboundIndex = 0xFE
OutboundMax = OutboundLogicalAnd OutboundMax = OutboundLogicalAnd
OutboundUserDefinedMax = OutboundMustDirect - 1 OutboundUserDefinedMax = OutboundMustDirect - 1

View File

@ -1,15 +1,17 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package consts package consts
type RoutingDomainKey string
const ( const (
RoutingDomain_Full = "full" RoutingDomainKey_Full RoutingDomainKey = "full"
RoutingDomain_Keyword = "keyword" RoutingDomainKey_Keyword RoutingDomainKey = "keyword"
RoutingDomain_Suffix = "suffix" RoutingDomainKey_Suffix RoutingDomainKey = "suffix"
RoutingDomain_Regex = "regex" RoutingDomainKey_Regex RoutingDomainKey = "regex"
Function_Domain = "domain" Function_Domain = "domain"
Function_Ip = "ip" Function_Ip = "ip"

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package common package common

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package netutils package netutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package netutils package netutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package netutils package netutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package netutils package netutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package common package common

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package dialer package dialer

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package dialer package dialer

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package dialer package dialer

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package dialer package dialer

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package dialer package dialer

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package dialer package dialer

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package outbound package outbound

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package outbound package outbound

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package outbound package outbound

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package outbound package outbound

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package outbound package outbound

View File

@ -0,0 +1,14 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2023, v2rayA Organization <team@v2raya.org>
*/
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)
}

View File

@ -0,0 +1,119 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2023, v2rayA Organization <team@v2raya.org>
*/
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
}

View File

@ -0,0 +1,236 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2023, v2rayA Organization <team@v2raya.org>
*/
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)
}
}

View File

@ -0,0 +1,67 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2023, v2rayA Organization <team@v2raya.org>
*/
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
}

View File

@ -0,0 +1,93 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2023, v2rayA Organization <team@v2raya.org>
*/
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
}

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package routing package routing
@ -19,6 +19,12 @@ var FakeOutbound_MUST_DIRECT = consts.OutboundMustDirect.String()
var FakeOutbound_AND = consts.OutboundLogicalAnd.String() var FakeOutbound_AND = consts.OutboundLogicalAnd.String()
var FakeOutbound_OR = consts.OutboundLogicalOr.String() var FakeOutbound_OR = consts.OutboundLogicalOr.String()
type DomainSet struct {
Key consts.RoutingDomainKey
RuleIndex int
Domains []string
}
type MatcherBuilder interface { type MatcherBuilder interface {
AddDomain(f *config_parser.Function, key string, values []string, outbound string) AddDomain(f *config_parser.Function, key string, values []string, outbound string)
AddIp(f *config_parser.Function, values []netip.Prefix, outbound string) AddIp(f *config_parser.Function, values []netip.Prefix, outbound string)
@ -32,7 +38,6 @@ type MatcherBuilder interface {
AddFallback(outbound string) AddFallback(outbound string)
AddAnyBefore(f *config_parser.Function, key string, values []string, outbound string) AddAnyBefore(f *config_parser.Function, key string, values []string, outbound string)
AddAnyAfter(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) { 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) AddAnyAfter(f *config_parser.Function, key string, values []string, outbound string) {
} }
func (d *DefaultMatcherBuilder) Build() (err error) { return nil }

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package routing package routing
@ -49,9 +49,9 @@ func (o *RefineFunctionParamKeyOptimizer) Optimize(rules []*config_parser.Routin
// Rewrite to authoritative key name. // Rewrite to authoritative key name.
switch param.Key { switch param.Key {
case "", "domain": case "", "domain":
param.Key = consts.RoutingDomain_Suffix param.Key = string(consts.RoutingDomainKey_Suffix)
case "contains": case "contains":
param.Key = consts.RoutingDomain_Keyword param.Key = string(consts.RoutingDomainKey_Keyword)
default: default:
} }
} }
@ -169,25 +169,25 @@ func (o *DatReaderOptimizer) loadGeoSite(filename string, code string) (params [
case geodata.Domain_Full: case geodata.Domain_Full:
// Full. // Full.
params = append(params, &config_parser.Param{ params = append(params, &config_parser.Param{
Key: consts.RoutingDomain_Full, Key: string(consts.RoutingDomainKey_Full),
Val: item.Value, Val: item.Value,
}) })
case geodata.Domain_RootDomain: case geodata.Domain_RootDomain:
// Suffix. // Suffix.
params = append(params, &config_parser.Param{ params = append(params, &config_parser.Param{
Key: consts.RoutingDomain_Suffix, Key: string(consts.RoutingDomainKey_Suffix),
Val: item.Value, Val: item.Value,
}) })
case geodata.Domain_Plain: case geodata.Domain_Plain:
// Keyword. // Keyword.
params = append(params, &config_parser.Param{ params = append(params, &config_parser.Param{
Key: consts.RoutingDomain_Keyword, Key: string(consts.RoutingDomainKey_Keyword),
Val: item.Value, Val: item.Value,
}) })
case geodata.Domain_Regex: case geodata.Domain_Regex:
// Regex. // Regex.
params = append(params, &config_parser.Param{ params = append(params, &config_parser.Param{
Key: consts.RoutingDomain_Regex, Key: string(consts.RoutingDomainKey_Regex),
Val: item.Value, Val: item.Value,
}) })
} }

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package quicutils package quicutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package quicutils package quicutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package quicutils package quicutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package quicutils package quicutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package quicutils package quicutils

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package sniffing package sniffing

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config package config

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config package config

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config package config

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config package config

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control
@ -48,6 +48,12 @@ func (r _bpfPortRange) Encode() (b [16]byte) {
return b 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) { func (o *bpfObjects) newLpmMap(keys []_bpfLpmKey, values []uint32) (m *ebpf.Map, err error) {
m, err = ebpf.NewMap(&ebpf.MapSpec{ m, err = ebpf.NewMap(&ebpf.MapSpec{
Type: ebpf.LPMTrie, Type: ebpf.LPMTrie,

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control
@ -43,16 +43,14 @@ type ControlPlane struct {
// TODO: add mutex? // TODO: add mutex?
outbounds []*outbound.DialerGroup outbounds []*outbound.DialerGroup
SimulatedLpmTries [][]netip.Prefix
SimulatedDomainSet []DomainSet
Fallback string
// mutex protects the dnsCache. // mutex protects the dnsCache.
dnsCacheMu sync.Mutex dnsCacheMu sync.Mutex
dnsCache map[string]*dnsCache dnsCache map[string]*dnsCache
dnsUpstream DnsUpstreamRaw dnsUpstream DnsUpstreamRaw
dialMode consts.DialMode dialMode consts.DialMode
routingMatcher *RoutingMatcher
} }
func NewControlPlane( func NewControlPlane(
@ -294,28 +292,30 @@ func NewControlPlane(
if err = routing.ApplyMatcherBuilder(log, builder, rules, routingA.Fallback); err != nil { if err = routing.ApplyMatcherBuilder(log, builder, rules, routingA.Fallback); err != nil {
return nil, fmt.Errorf("ApplyMatcherBuilder: %w", err) return nil, fmt.Errorf("ApplyMatcherBuilder: %w", err)
} }
if err = builder.Build(); err != nil { if err = builder.BuildKernspace(); err != nil {
return nil, fmt.Errorf("RoutingMatcherBuilder.Build: %w", err) 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) dialMode, err := consts.ParseDialMode(global.DialMode)
c = &ControlPlane{ c = &ControlPlane{
log: log, log: log,
core: core, core: core,
deferFuncs: nil, deferFuncs: nil,
listenIp: "0.0.0.0", listenIp: "0.0.0.0",
outbounds: outbounds, outbounds: outbounds,
SimulatedLpmTries: builder.SimulatedLpmTries, dnsCacheMu: sync.Mutex{},
SimulatedDomainSet: builder.SimulatedDomainSet, dnsCache: make(map[string]*dnsCache),
Fallback: routingA.Fallback,
dnsCacheMu: sync.Mutex{},
dnsCache: make(map[string]*dnsCache),
dnsUpstream: DnsUpstreamRaw{ dnsUpstream: DnsUpstreamRaw{
Raw: global.DnsUpstream, Raw: global.DnsUpstream,
FinishInitCallback: nil, FinishInitCallback: nil,
}, },
dialMode: dialMode, dialMode: dialMode,
routingMatcher: routingMatcher,
} }
/// DNS upstream /// DNS upstream

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control
@ -13,7 +13,6 @@ import (
"github.com/mohae/deepcopy" "github.com/mohae/deepcopy"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/v2rayA/dae/common" "github.com/v2rayA/dae/common"
"github.com/v2rayA/dae/common/consts"
"golang.org/x/net/dns/dnsmessage" "golang.org/x/net/dns/dnsmessage"
"hash/fnv" "hash/fnv"
"math/rand" "math/rand"
@ -28,7 +27,7 @@ var (
) )
type dnsCache struct { type dnsCache struct {
DomainBitmap [consts.MaxMatchSetLen / 32]uint32 DomainBitmap []uint32
Answers []dnsmessage.Resource Answers []dnsmessage.Resource
Deadline time.Time Deadline time.Time
} }
@ -95,8 +94,12 @@ func (c *ControlPlane) BatchUpdateDomainRouting(cache *dnsCache) error {
ip6 := ip.As16() ip6 := ip.As16()
keys = append(keys, common.Ipv6ByteSliceToUint32Array(ip6[:])) keys = append(keys, common.Ipv6ByteSliceToUint32Array(ip6[:]))
vals = append(vals, bpfDomainRouting{ 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{ if _, err := BpfMapBatchUpdate(c.core.bpf.DomainRoutingMap, keys, vals, &ebpf.BatchOptions{
ElemFlags: uint64(ebpf.UpdateAny), ElemFlags: uint64(ebpf.UpdateAny),
@ -341,7 +344,7 @@ func (c *ControlPlane) UpdateDnsCache(host string, typ dnsmessage.Type, answers
cache.Answers = answers cache.Answers = answers
} else { } else {
cache = &dnsCache{ cache = &dnsCache{
DomainBitmap: c.MatchDomainBitmap(strings.TrimSuffix(fqdn, ".")), DomainBitmap: c.routingMatcher.domainMatcher.MatchDomainBitmap(fqdn),
Answers: answers, Answers: answers,
Deadline: deadline, Deadline: deadline,
} }

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,48 +0,0 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org>
*/
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
}

View File

@ -1,7 +1,7 @@
// +build ignore // +build ignore
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
#include <asm-generic/errno-base.h> #include <asm-generic/errno-base.h>

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control
@ -8,28 +8,24 @@ package control
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/Asphaltt/lpmtrie"
"github.com/cilium/ebpf" "github.com/cilium/ebpf"
"github.com/v2rayA/dae/common" "github.com/v2rayA/dae/common"
"github.com/v2rayA/dae/common/consts" "github.com/v2rayA/dae/common/consts"
"github.com/v2rayA/dae/component/routing" "github.com/v2rayA/dae/component/routing"
"github.com/v2rayA/dae/component/routing/domain_matcher"
"github.com/v2rayA/dae/pkg/config_parser" "github.com/v2rayA/dae/pkg/config_parser"
"net/netip" "net/netip"
"strconv" "strconv"
) )
type DomainSet struct {
Key string
RuleIndex int
Domains []string
}
type RoutingMatcherBuilder struct { type RoutingMatcherBuilder struct {
*routing.DefaultMatcherBuilder *routing.DefaultMatcherBuilder
outboundName2Id map[string]uint8 outboundName2Id map[string]uint8
bpf *bpfObjects bpf *bpfObjects
rules []bpfMatchSet rules []bpfMatchSet
SimulatedLpmTries [][]netip.Prefix simulatedLpmTries [][]netip.Prefix
SimulatedDomainSet []DomainSet simulatedDomainSet []routing.DomainSet
Fallback string Fallback string
err error err error
@ -62,17 +58,17 @@ func (b *RoutingMatcherBuilder) AddDomain(f *config_parser.Function, key string,
if b.err != nil { if b.err != nil {
return return
} }
switch key { switch consts.RoutingDomainKey(key) {
case consts.RoutingDomain_Regex, case consts.RoutingDomainKey_Regex,
consts.RoutingDomain_Full, consts.RoutingDomainKey_Full,
consts.RoutingDomain_Keyword, consts.RoutingDomainKey_Keyword,
consts.RoutingDomain_Suffix: consts.RoutingDomainKey_Suffix:
default: default:
b.err = fmt.Errorf("AddDomain: unsupported key: %v", key) b.err = fmt.Errorf("AddDomain: unsupported key: %v", key)
return return
} }
b.SimulatedDomainSet = append(b.SimulatedDomainSet, DomainSet{ b.simulatedDomainSet = append(b.simulatedDomainSet, routing.DomainSet{
Key: key, Key: consts.RoutingDomainKey(key),
RuleIndex: len(b.rules), RuleIndex: len(b.rules),
Domains: values, Domains: values,
}) })
@ -94,8 +90,8 @@ func (b *RoutingMatcherBuilder) AddSourceMac(f *config_parser.Function, macAddrs
prefix := netip.PrefixFrom(netip.AddrFrom16(addr16), 128) prefix := netip.PrefixFrom(netip.AddrFrom16(addr16), 128)
values = append(values, prefix) values = append(values, prefix)
} }
lpmTrieIndex := len(b.SimulatedLpmTries) lpmTrieIndex := len(b.simulatedLpmTries)
b.SimulatedLpmTries = append(b.SimulatedLpmTries, values) b.simulatedLpmTries = append(b.simulatedLpmTries, values)
set := bpfMatchSet{ set := bpfMatchSet{
Value: [16]byte{}, Value: [16]byte{},
Type: uint8(consts.MatchType_Mac), Type: uint8(consts.MatchType_Mac),
@ -111,8 +107,8 @@ func (b *RoutingMatcherBuilder) AddIp(f *config_parser.Function, values []netip.
if b.err != nil { if b.err != nil {
return return
} }
lpmTrieIndex := len(b.SimulatedLpmTries) lpmTrieIndex := len(b.simulatedLpmTries)
b.SimulatedLpmTries = append(b.SimulatedLpmTries, values) b.simulatedLpmTries = append(b.simulatedLpmTries, values)
set := bpfMatchSet{ set := bpfMatchSet{
Value: [16]byte{}, Value: [16]byte{},
Type: uint8(consts.MatchType_IpSet), Type: uint8(consts.MatchType_IpSet),
@ -145,8 +141,8 @@ func (b *RoutingMatcherBuilder) AddSourceIp(f *config_parser.Function, values []
if b.err != nil { if b.err != nil {
return return
} }
lpmTrieIndex := len(b.SimulatedLpmTries) lpmTrieIndex := len(b.simulatedLpmTries)
b.SimulatedLpmTries = append(b.SimulatedLpmTries, values) b.simulatedLpmTries = append(b.simulatedLpmTries, values)
set := bpfMatchSet{ set := bpfMatchSet{
Value: [16]byte{}, Value: [16]byte{},
Type: uint8(consts.MatchType_SourceIpSet), 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 { if b.err != nil {
return b.err return b.err
} }
// Update lpm_array_map. // Update lpm_array_map.
for i, cidrs := range b.SimulatedLpmTries { for i, cidrs := range b.simulatedLpmTries {
var keys []_bpfLpmKey var keys []_bpfLpmKey
var values []uint32 var values []uint32
for _, cidr := range cidrs { for _, cidr := range cidrs {
@ -264,3 +260,40 @@ func (b *RoutingMatcherBuilder) Build() (err error) {
} }
return nil 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
}

View File

@ -0,0 +1,150 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2023, v2rayA Organization <team@v2raya.org>
*/
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[:],
}
}

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package control package control

2
go.mod
View File

@ -3,9 +3,11 @@ module github.com/v2rayA/dae
go 1.18 go 1.18
require ( require (
github.com/Asphaltt/lpmtrie v0.0.0-20220205153150-3d814250b8ab
github.com/adrg/xdg v0.4.0 github.com/adrg/xdg v0.4.0
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12
github.com/cilium/ebpf v0.10.0 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/gorilla/websocket v1.5.0
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826

4
go.sum
View File

@ -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 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= 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 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= 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 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE= 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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=

View File

@ -2,7 +2,7 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package main package main

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config_parser package config_parser

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config_parser package config_parser

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config_parser package config_parser

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config_parser package config_parser

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2022, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package config_parser package config_parser

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
// Modified from https://github.com/v2fly/v2ray-core/blob/42b166760b2ba8d984e514b830fcd44e23728e43/infra/conf/geodata/memconservative // Modified from https://github.com/v2fly/v2ray-core/blob/42b166760b2ba8d984e514b830fcd44e23728e43/infra/conf/geodata/memconservative

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
// Modified from https://github.com/v2fly/v2ray-core/blob/42b166760b2ba8d984e514b830fcd44e23728e43/infra/conf/geodata/memconservative // Modified from https://github.com/v2fly/v2ray-core/blob/42b166760b2ba8d984e514b830fcd44e23728e43/infra/conf/geodata/memconservative

View File

@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org> * Copyright (c) 2022-2023, v2rayA Organization <team@v2raya.org>
*/ */
package logger package logger