feat: nameserver-policy support use rule-providers and reduce domain-set memory

This commit is contained in:
Skyxim 2023-04-01 11:53:39 +08:00
parent 991de009be
commit cfd03a99c2
30 changed files with 503 additions and 141 deletions

View file

@ -0,0 +1,15 @@
//go:build with_low_memory
package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 16 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 8 * 1024
)

View file

@ -0,0 +1,15 @@
//go:build !with_low_memory
package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024
)

View file

@ -1,17 +1,5 @@
package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024
)
func Get(size int) []byte {
return defaultAllocator.Get(size)
}

9
common/utils/strings.go Normal file
View file

@ -0,0 +1,9 @@
package utils
func Reverse(s string) string {
a := []rune(s)
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
return string(a)
}

View file

@ -28,8 +28,8 @@ var Dispatcher *SnifferDispatcher
type SnifferDispatcher struct {
enable bool
sniffers map[sniffer.Sniffer]SnifferConfig
forceDomain *trie.DomainTrie[struct{}]
skipSNI *trie.DomainTrie[struct{}]
forceDomain *trie.DomainSet
skipSNI *trie.DomainSet
skipList *cache.LruCache[string, uint8]
rwMux sync.RWMutex
forceDnsMapping bool
@ -37,7 +37,7 @@ type SnifferDispatcher struct {
}
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) {
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Search(metadata.Host) != nil || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
if err != nil {
log.Debugln("[Sniffer] Dst port is error")
@ -74,7 +74,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return
} else {
if sd.skipSNI.Search(host) != nil {
if sd.skipSNI.Has(host) {
log.Debugln("[Sniffer] Skip sni[%s]", host)
return
}
@ -166,8 +166,8 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil
}
func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, forceDomain *trie.DomainTrie[struct{}],
skipSNI *trie.DomainTrie[struct{}],
func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig,
forceDomain *trie.DomainSet, skipSNI *trie.DomainSet,
forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{
enable: true,

View file

@ -15,7 +15,7 @@ import (
)
var trustCerts []*x509.Certificate
var certPool *x509.CertPool
var mutex sync.RWMutex
var errNotMacth error = errors.New("certificate fingerprints do not match")
@ -40,10 +40,20 @@ func ResetCertificate() {
}
func getCertPool() *x509.CertPool {
certPool, err := x509.SystemCertPool()
if err == nil {
for _, cert := range trustCerts {
certPool.AddCert(cert)
if len(trustCerts) == 0 {
return nil
}
if certPool == nil {
mutex.Lock()
defer mutex.Unlock()
if certPool != nil {
return certPool
}
certPool, err := x509.SystemCertPool()
if err == nil {
for _, cert := range trustCerts {
certPool.AddCert(cert)
}
}
}
return certPool

View file

@ -25,7 +25,7 @@ func ValidAndSplitDomain(domain string) ([]string, bool) {
if domain != "" && domain[len(domain)-1] == '.' {
return nil, false
}
domain=strings.ToLower(domain)
parts := strings.Split(domain, domainStep)
if len(parts) == 1 {
if parts[0] == "" {
@ -123,6 +123,30 @@ func (t *DomainTrie[T]) Optimize() {
t.root.optimize()
}
func (t *DomainTrie[T]) Foreach(print func(domain string, data T)) {
for key, data := range t.root.getChildren() {
recursion([]string{key}, data, print)
}
}
func recursion[T any](items []string, node *Node[T], fn func(domain string, data T)) {
for key, data := range node.getChildren() {
newItems := append([]string{key}, items...)
if data != nil && data.inited {
domain := joinDomain(newItems)
if domain[0] == domainStepByte {
domain = complexWildcard + domain
}
fn(domain, data.Data())
}
recursion(newItems, data, fn)
}
}
func joinDomain(items []string) string {
return strings.Join(items, domainStep)
}
// New returns a new, empty Trie.
func New[T any]() *DomainTrie[T] {
return &DomainTrie[T]{root: newNode[T]()}

View file

@ -105,3 +105,23 @@ func TestTrie_WildcardBoundary(t *testing.T) {
assert.NotNil(t, tree.Search("example.com"))
}
func TestTrie_Foreach(t *testing.T) {
tree := New[netip.Addr]()
domainList := []string{
"google.com",
"stun.*.*.*",
"test.*.google.com",
"+.baidu.com",
"*.baidu.com",
"*.*.baidu.com",
}
for _, domain := range domainList {
tree.Insert(domain, localIP)
}
count := 0
tree.Foreach(func(domain string, data netip.Addr) {
count++
})
assert.Equal(t, 7, count)
}

View file

@ -116,6 +116,18 @@ func (n *Node[T]) setData(data T) {
n.inited = true
}
func (n *Node[T]) getChildren() map[string]*Node[T] {
if n.childMap == nil {
if n.childNode != nil {
m := make(map[string]*Node[T])
m[n.childStr] = n.childNode
return m
}
} else {
return n.childMap
}
return nil
}
func (n *Node[T]) Data() T {
return n.data
}

View file

@ -0,0 +1,60 @@
package trie_test
import (
"testing"
"github.com/Dreamacro/clash/component/trie"
"github.com/stretchr/testify/assert"
)
func TestDomain(t *testing.T) {
domainSet := []string{
"baidu.com",
"google.com",
"www.google.com",
"test.a.net",
"test.a.oc",
}
set := trie.NewDomainSet(domainSet)
assert.NotNil(t, set)
assert.True(t, set.Has("test.a.net"))
assert.True(t, set.Has("google.com"))
assert.False(t, set.Has("www.baidu.com"))
}
func TestDomainComplexWildcard(t *testing.T) {
domainSet := []string{
"+.baidu.com",
"+.a.baidu.com",
"www.baidu.com",
"+.bb.baidu.com",
"test.a.net",
"test.a.oc",
"www.qq.com",
}
set := trie.NewDomainSet(domainSet)
assert.NotNil(t, set)
assert.False(t, set.Has("google.com"))
assert.True(t, set.Has("www.baidu.com"))
assert.True(t, set.Has("test.test.baidu.com"))
}
func TestDomainWildcard(t *testing.T) {
domainSet := []string{
"*.*.*.baidu.com",
"www.baidu.*",
"stun.*.*",
"*.*.qq.com",
"test.*.baidu.com",
}
set := trie.NewDomainSet(domainSet)
assert.NotNil(t, set)
assert.True(t, set.Has("www.baidu.com"))
assert.True(t, set.Has("test.test.baidu.com"))
assert.True(t, set.Has("test.test.qq.com"))
assert.True(t,set.Has("stun.ab.cd"))
assert.False(t, set.Has("test.baidu.com"))
assert.False(t,set.Has("www.google.com"))
assert.False(t, set.Has("test.qq.com"))
assert.False(t, set.Has("test.test.test.qq.com"))
}

178
component/trie/sskv.go Normal file
View file

@ -0,0 +1,178 @@
package trie
// Package succinct provides several succinct data types.
// Modify from https://github.com/openacid/succinct/blob/d4684c35d123f7528b14e03c24327231723db704/sskv.go
import (
"sort"
"strings"
"github.com/Dreamacro/clash/common/utils"
"github.com/openacid/low/bitmap"
)
const (
complexWildcardByte = byte('+')
wildcardByte = byte('*')
domainStepByte = byte('.')
)
type DomainSet struct {
leaves, labelBitmap []uint64
labels []byte
ranks, selects []int32
}
// NewDomainSet creates a new *DomainSet struct, from a slice of sorted strings.
func NewDomainSet(keys []string) *DomainSet {
domainTrie := New[struct{}]()
for _, domain := range keys {
domainTrie.Insert(domain, struct{}{})
}
reserveDomains := make([]string, 0, len(keys))
domainTrie.Foreach(func(domain string, data struct{}) {
reserveDomains = append(reserveDomains, utils.Reverse(domain))
})
// ensure that the same prefix is continuous
// and according to the ascending sequence of length
sort.Strings(reserveDomains)
keys = reserveDomains
if len(keys) == 0 {
return nil
}
ss := &DomainSet{}
lIdx := 0
type qElt struct{ s, e, col int }
queue := []qElt{{0, len(keys), 0}}
for i := 0; i < len(queue); i++ {
elt := queue[i]
if elt.col == len(keys[elt.s]) {
elt.s++
// a leaf node
setBit(&ss.leaves, i, 1)
}
for j := elt.s; j < elt.e; {
frm := j
for ; j < elt.e && keys[j][elt.col] == keys[frm][elt.col]; j++ {
}
queue = append(queue, qElt{frm, j, elt.col + 1})
ss.labels = append(ss.labels, keys[frm][elt.col])
setBit(&ss.labelBitmap, lIdx, 0)
lIdx++
}
setBit(&ss.labelBitmap, lIdx, 1)
lIdx++
}
ss.init()
return ss
}
// Has query for a key and return whether it presents in the DomainSet.
func (ss *DomainSet) Has(key string) bool {
if ss == nil {
return false
}
key = utils.Reverse(key)
key = strings.ToLower(key)
// no more labels in this node
// skip character matching
// go to next level
nodeId, bmIdx := 0, 0
type wildcardCursor struct {
bmIdx, index int
}
stack := make([]wildcardCursor, 0)
for i := 0; i < len(key); i++ {
RESTART:
c := key[i]
for ; ; bmIdx++ {
if getBit(ss.labelBitmap, bmIdx) != 0 {
if len(stack) > 0 {
cursor := stack[len(stack)-1]
stack = stack[0 : len(stack)-1]
// back wildcard and find next node
nextNodeId := countZeros(ss.labelBitmap, ss.ranks, cursor.bmIdx+1)
nextBmIdx := selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nextNodeId-1) + 1
j := cursor.index
for ; j < len(key) && key[j] != domainStepByte; j++ {
}
if j == len(key) {
if getBit(ss.leaves, nextNodeId) != 0 {
return true
}else {
goto RESTART
}
}
for ; ; nextBmIdx++ {
if ss.labels[nextBmIdx-nextNodeId] == domainStepByte {
bmIdx = nextBmIdx
nodeId = nextNodeId
i = j
goto RESTART
}
}
}
return false
}
// handle wildcard for domain
if ss.labels[bmIdx-nodeId] == complexWildcardByte {
return true
} else if ss.labels[bmIdx-nodeId] == wildcardByte {
cursor := wildcardCursor{}
cursor.bmIdx = bmIdx
cursor.index = i
stack = append(stack, cursor)
} else if ss.labels[bmIdx-nodeId] == c {
break
}
}
nodeId = countZeros(ss.labelBitmap, ss.ranks, bmIdx+1)
bmIdx = selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nodeId-1) + 1
}
return getBit(ss.leaves, nodeId) != 0
}
func setBit(bm *[]uint64, i int, v int) {
for i>>6 >= len(*bm) {
*bm = append(*bm, 0)
}
(*bm)[i>>6] |= uint64(v) << uint(i&63)
}
func getBit(bm []uint64, i int) uint64 {
return bm[i>>6] & (1 << uint(i&63))
}
// init builds pre-calculated cache to speed up rank() and select()
func (ss *DomainSet) init() {
ss.selects, ss.ranks = bitmap.IndexSelect32R64(ss.labelBitmap)
}
// countZeros counts the number of "0" in a bitmap before the i-th bit(excluding
// the i-th bit) on behalf of rank index.
// E.g.:
//
// countZeros("010010", 4) == 3
// // 012345
func countZeros(bm []uint64, ranks []int32, i int) int {
a, _ := bitmap.Rank64(bm, ranks, int32(i))
return i - int(a)
}
// selectIthOne returns the index of the i-th "1" in a bitmap, on behalf of rank
// and select indexes.
// E.g.:
//
// selectIthOne("010010", 1) == 4
// // 012345
func selectIthOne(bm []uint64, ranks, selects []int32, i int) int {
a, _ := bitmap.Select32R64(bm, selects, ranks, int32(i))
return int(a)
}

View file

@ -9,7 +9,6 @@ import (
"net/url"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"time"
@ -136,9 +135,8 @@ type IPTables struct {
type Sniffer struct {
Enable bool
Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig
Reverses *trie.DomainTrie[struct{}]
ForceDomain *trie.DomainTrie[struct{}]
SkipDomain *trie.DomainTrie[struct{}]
ForceDomain *trie.DomainSet
SkipDomain *trie.DomainSet
ForceDnsMapping bool
ParsePureIp bool
}
@ -490,7 +488,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.Hosts = hosts
dnsCfg, err := parseDNS(rawCfg, hosts, rules)
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
if err != nil {
return nil, err
}
@ -822,8 +820,6 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
rules = append(rules, parsed)
}
runtime.GC()
return rules, nil
}
@ -983,7 +979,7 @@ func parsePureDNSServer(server string) string {
}
}
}
func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) {
func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]providerTypes.RuleProvider, preferH3 bool) (map[string][]dns.NameServer, error) {
policy := map[string][]dns.NameServer{}
updatedPolicy := make(map[string]interface{})
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
@ -998,6 +994,14 @@ func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][
newKey := "geosite:" + subkey
updatedPolicy[newKey] = v
}
} else if strings.Contains(k, "rule-set:") {
subkeys := strings.Split(k, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, subkey := range subkeys {
newKey := "rule-set:" + subkey
updatedPolicy[newKey] = v
}
} else if re.MatchString(k) {
subkeys := strings.Split(k, ",")
for _, subkey := range subkeys {
@ -1021,6 +1025,19 @@ func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
}
if strings.HasPrefix(domain, "rule-set:") {
domainSetName := domain[9:]
if provider, ok := ruleProviders[domainSetName]; !ok {
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
} else {
switch provider.Behavior() {
case providerTypes.IPCIDR:
return nil, fmt.Errorf("rule provider type error, except domain,actual %s", provider.Behavior())
case providerTypes.Classical:
log.Warnln("%s provider is %s, only matching it contain domain rule", provider.Name(), provider.Behavior())
}
}
}
policy[domain] = nameservers
}
@ -1073,11 +1090,10 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %d", country, recordsCount)
}
}
runtime.GC()
return sites, nil
}
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule) (*DNS, error) {
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
@ -1104,7 +1120,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, err
}
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, cfg.PreferH3); err != nil {
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.PreferH3); err != nil {
return nil, err
}
@ -1324,24 +1340,8 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
}
sniffer.Sniffers = loadSniffer
sniffer.ForceDomain = trie.New[struct{}]()
for _, domain := range snifferRaw.ForceDomain {
err := sniffer.ForceDomain.Insert(domain, struct{}{})
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
}
sniffer.ForceDomain.Optimize()
sniffer.SkipDomain = trie.New[struct{}]()
for _, domain := range snifferRaw.SkipDomain {
err := sniffer.SkipDomain.Insert(domain, struct{}{})
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
}
sniffer.SkipDomain.Optimize()
sniffer.ForceDomain = trie.NewDomainSet(snifferRaw.ForceDomain)
sniffer.SkipDomain = trie.NewDomainSet(snifferRaw.SkipDomain)
return sniffer, nil
}

View file

@ -0,0 +1,5 @@
package features
func init() {
TAGS = append(TAGS, "with_low_memory")
}

View file

@ -1,7 +0,0 @@
//go:build no_doq
package features
func init() {
TAGS = append(TAGS, "no_doq")
}

View file

@ -0,0 +1,7 @@
//go:build no_fake_tcp
package features
func init() {
TAGS = append(TAGS, "no_fake_tcp")
}

View file

@ -1,7 +0,0 @@
//go:build no_gvisor
package features
func init() {
TAGS = append(TAGS, "no_gvisor")
}

View file

@ -0,0 +1,7 @@
//go:build with_gvisor
package features
func init() {
TAGS = append(TAGS, "with_gvisor")
}

View file

@ -16,6 +16,7 @@ import (
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
@ -40,6 +41,11 @@ type geositePolicyRecord struct {
inversedMatching bool
}
type domainSetPolicyRecord struct {
domainSetProvider provider.RuleProvider
policy *Policy
}
type Resolver struct {
ipv6 bool
ipv6Timeout time.Duration
@ -51,6 +57,7 @@ type Resolver struct {
group singleflight.Group
lruCache *cache.LruCache[string, *D.Msg]
policy *trie.DomainTrie[*Policy]
domainSetPolicy []domainSetPolicyRecord
geositePolicy []geositePolicyRecord
proxyServer []dnsClient
}
@ -301,6 +308,12 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
return geositeRecord.policy.GetData()
}
}
metadata := &C.Metadata{Host: domain}
for _, domainSetRecord := range r.domainSetPolicy {
if ok := domainSetRecord.domainSetProvider.Match(metadata); ok {
return domainSetRecord.policy.GetData()
}
}
return nil
}
@ -422,16 +435,18 @@ type FallbackFilter struct {
}
type Config struct {
Main, Fallback []NameServer
Default []NameServer
ProxyServer []NameServer
IPv6 bool
IPv6Timeout uint
EnhancedMode C.DNSMode
FallbackFilter FallbackFilter
Pool *fakeip.Pool
Hosts *trie.DomainTrie[resolver.HostValue]
Policy map[string][]NameServer
Main, Fallback []NameServer
Default []NameServer
ProxyServer []NameServer
IPv6 bool
IPv6Timeout uint
EnhancedMode C.DNSMode
FallbackFilter FallbackFilter
Pool *fakeip.Pool
Hosts *trie.DomainTrie[resolver.HostValue]
Policy map[string][]NameServer
DomainSetPolicy map[provider.RuleProvider][]NameServer
GeositePolicy map[router.DomainMatcher][]NameServer
}
func NewResolver(config Config) *Resolver {
@ -483,6 +498,14 @@ func NewResolver(config Config) *Resolver {
}
r.policy.Optimize()
}
if len(config.DomainSetPolicy) > 0 {
for p, n := range config.DomainSetPolicy {
r.domainSetPolicy = append(r.domainSetPolicy, domainSetPolicyRecord{
domainSetProvider: p,
policy: NewPolicy(transform(n, defaultResolver)),
})
}
}
fallbackIPFilters := []fallbackIPFilter{}
if config.FallbackFilter.GeoIP {

View file

@ -238,6 +238,7 @@ dns:
- https://doh.pub/dns-query
- https://dns.alidns.com/dns-query
"www.baidu.com,+.google.cn": [223.5.5.5, https://dns.alidns.com/dns-query]
# "rule-set:global,dns": 8.8.8.8 # globaldns 为 rule-providers 中的名为 global 和 dns 的规则提供器名字,且 behavior 必须为 domain
proxies: # socks5
- name: "socks"

6
go.mod
View file

@ -51,6 +51,11 @@ require (
lukechampine.com/blake3 v1.1.7
)
require (
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
)
require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
@ -67,6 +72,7 @@ require (
github.com/mdlayher/socket v0.4.0 // indirect
github.com/metacubex/gvisor v0.0.0-20230323114922-412956fb6a03 // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect

16
go.sum
View file

@ -6,6 +6,7 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -34,6 +35,7 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
@ -75,6 +77,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
@ -105,13 +109,21 @@ github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/mroth/weightedrand/v2 v2.0.0 h1:ADehnByWbliEDIazDAKFdBHoqgHSXAkgyKqM/9YsPoo=
github.com/mroth/weightedrand/v2 v2.0.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs3U1+I=
github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do=
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
@ -148,6 +160,7 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -243,7 +256,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ=
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -5,6 +5,7 @@ import (
"net/netip"
"os"
"runtime"
"strings"
"sync"
"github.com/Dreamacro/clash/adapter"
@ -91,7 +92,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateSniffer(cfg.Sniffer)
updateHosts(cfg.Hosts)
updateGeneral(cfg.General)
updateDNS(cfg.DNS, cfg.General.IPv6)
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
updateListeners(cfg.General, cfg.Listeners, force)
updateIPTables(cfg)
updateTun(cfg.General)
@ -104,7 +105,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
loadProxyProvider(cfg.Providers)
updateProfile(cfg)
loadRuleProvider(cfg.RuleProviders)
runtime.GC()
tunnel.OnRunning()
log.SetLevel(cfg.General.LogLevel)
@ -175,10 +176,9 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
}
func updateExperimental(c *config.Config) {
runtime.GC()
}
func updateDNS(c *config.DNS, generalIPv6 bool) {
func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
if !c.Enable {
resolver.DefaultResolver = nil
resolver.DefaultHostMapper = nil
@ -186,7 +186,25 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
dns.ReCreateServer("", nil, nil)
return
}
policy := make(map[string][]dns.NameServer)
domainSetPolicies := make(map[provider.RuleProvider][]dns.NameServer)
for key, nameservers := range c.NameServerPolicy {
temp := strings.Split(key, ":")
if len(temp) == 2 {
prefix := temp[0]
key := temp[1]
switch strings.ToLower(prefix) {
case "rule-set":
if p, ok := ruleProvider[key]; ok {
domainSetPolicies[p] = nameservers
}
case "geosite":
// TODO:
}
} else {
policy[key] = nameservers
}
}
cfg := dns.Config{
Main: c.NameServer,
Fallback: c.Fallback,
@ -202,9 +220,10 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
Domain: c.FallbackFilter.Domain,
GeoSite: c.FallbackFilter.GeoSite,
},
Default: c.DefaultNameserver,
Policy: c.NameServerPolicy,
ProxyServer: c.ProxyServerNameserver,
Default: c.DefaultNameserver,
Policy: c.NameServerPolicy,
ProxyServer: c.ProxyServerNameserver,
DomainSetPolicy: domainSetPolicies,
}
r := dns.NewResolver(cfg)

View file

@ -110,7 +110,10 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
conn2 = nil
}()
for {
buff := buf.NewPacket()
// safe size which is 1232 from https://dnsflagday.net/2020/.
// so 2048 is enough
buff := buf.NewSize(2 * 1024)
_ = conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout))
dest, err := conn.ReadPacket(buff)
if err != nil {
buff.Release()

View file

@ -1,7 +1,6 @@
package common
import (
"golang.org/x/net/idna"
"strings"
C "github.com/Dreamacro/clash/constant"
@ -11,7 +10,6 @@ type Domain struct {
*Base
domain string
adapter string
isIDNA bool
}
func (d *Domain) RuleType() C.RuleType {
@ -27,20 +25,14 @@ func (d *Domain) Adapter() string {
}
func (d *Domain) Payload() string {
domain := d.domain
if d.isIDNA {
domain, _ = idna.ToUnicode(domain)
}
return domain
return d.domain
}
func NewDomain(domain string, adapter string) *Domain {
actualDomain, _ := idna.ToASCII(domain)
return &Domain{
Base: &Base{},
domain: strings.ToLower(actualDomain),
domain: strings.ToLower(domain),
adapter: adapter,
isIDNA: actualDomain != domain,
}
}

View file

@ -1,7 +1,6 @@
package common
import (
"golang.org/x/net/idna"
"strings"
C "github.com/Dreamacro/clash/constant"
@ -11,7 +10,6 @@ type DomainKeyword struct {
*Base
keyword string
adapter string
isIDNA bool
}
func (dk *DomainKeyword) RuleType() C.RuleType {
@ -28,20 +26,14 @@ func (dk *DomainKeyword) Adapter() string {
}
func (dk *DomainKeyword) Payload() string {
keyword := dk.keyword
if dk.isIDNA {
keyword, _ = idna.ToUnicode(keyword)
}
return keyword
return dk.keyword
}
func NewDomainKeyword(keyword string, adapter string) *DomainKeyword {
actualDomainKeyword, _ := idna.ToASCII(keyword)
return &DomainKeyword{
Base: &Base{},
keyword: strings.ToLower(actualDomainKeyword),
keyword: strings.ToLower(keyword),
adapter: adapter,
isIDNA: keyword != actualDomainKeyword,
}
}

View file

@ -1,7 +1,6 @@
package common
import (
"golang.org/x/net/idna"
"strings"
C "github.com/Dreamacro/clash/constant"
@ -11,7 +10,6 @@ type DomainSuffix struct {
*Base
suffix string
adapter string
isIDNA bool
}
func (ds *DomainSuffix) RuleType() C.RuleType {
@ -28,20 +26,14 @@ func (ds *DomainSuffix) Adapter() string {
}
func (ds *DomainSuffix) Payload() string {
suffix := ds.suffix
if ds.isIDNA {
suffix, _ = idna.ToUnicode(suffix)
}
return suffix
return ds.suffix
}
func NewDomainSuffix(suffix string, adapter string) *DomainSuffix {
actualDomainSuffix, _ := idna.ToASCII(suffix)
return &DomainSuffix{
Base: &Base{},
suffix: strings.ToLower(actualDomainSuffix),
suffix: strings.ToLower(suffix),
adapter: adapter,
isIDNA: suffix != actualDomainSuffix,
}
}

View file

@ -21,10 +21,8 @@ func NewNetworkType(network, adapter string) (*NetworkType, error) {
switch strings.ToUpper(network) {
case "TCP":
ntType.network = C.TCP
break
case "UDP":
ntType.network = C.UDP
break
default:
return nil, fmt.Errorf("unsupported network type, only TCP/UDP")
}

View file

@ -3,13 +3,11 @@ package provider
import (
"github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"golang.org/x/net/idna"
)
type domainStrategy struct {
count int
domainRules *trie.DomainTrie[struct{}]
domainRules *trie.DomainSet
}
func (d *domainStrategy) ShouldFindProcess() bool {
@ -17,7 +15,7 @@ func (d *domainStrategy) ShouldFindProcess() bool {
}
func (d *domainStrategy) Match(metadata *C.Metadata) bool {
return d.domainRules != nil && d.domainRules.Search(metadata.RuleHost()) != nil
return d.domainRules != nil && d.domainRules.Has(metadata.RuleHost())
}
func (d *domainStrategy) Count() int {
@ -29,21 +27,9 @@ func (d *domainStrategy) ShouldResolveIP() bool {
}
func (d *domainStrategy) OnUpdate(rules []string) {
domainTrie := trie.New[struct{}]()
count := 0
for _, rule := range rules {
actualDomain, _ := idna.ToASCII(rule)
err := domainTrie.Insert(actualDomain, struct{}{})
if err != nil {
log.Warnln("invalid domain:[%s]", rule)
} else {
count++
}
}
domainTrie.Optimize()
domainTrie := trie.NewDomainSet(rules)
d.domainRules = domainTrie
d.count = count
d.count = len(rules)
}
func NewDomainStrategy() *domainStrategy {

View file

@ -1,5 +1,5 @@
//go:build linux
// +build linux
//go:build linux && !no_fake_tcp
// +build linux,!no_fake_tcp
package faketcp

View file

@ -1,5 +1,5 @@
//go:build !linux
// +build !linux
//go:build !linux || no_fake_tcp
// +build !linux no_fake_tcp
package faketcp