Compare commits

...

16 Commits

Author SHA1 Message Date
adlyq
8b09db5f7f fix: Rule-Set中不解析DNS
feat: RULE-SET支持no-resolve
2022-05-18 18:43:44 +08:00
adlyq
b5623602f5 chore: Android auto-detect-interface plus 2022-05-18 12:00:57 +08:00
Skyxim
16b27b3a1f fix: doq过代理错误 2022-05-17 21:30:54 +08:00
Skyxim
8b00be9039 fix: 删除udp触发的错误逻辑 2022-05-17 21:23:28 +08:00
Skyxim
fa9e27c5e4 refactor: 重构失败主动健康检测 2022-05-17 21:15:14 +08:00
adlyq
f4d9384603 chore: debug log print dns result 2022-05-17 18:21:18 +08:00
adlyq
c4408612b3 chore: 暴露数据给前端 2022-05-17 16:47:21 +08:00
Skyxim
0742f7db26 refactor: 重构StickySessions 2022-05-17 13:28:54 +08:00
Skyxim
891c2fe899 fix: 当dns被禁用时,dns将根据general ipv6设置解析dns 2022-05-17 09:01:41 +08:00
adlyq
b831eb178b chore: remove noisy log 2022-05-16 18:20:13 +08:00
adlyq
962ceaa89e refactor: strategyStickySessions 2022-05-16 17:46:28 +08:00
adlyq
d52b00bd34 refactor: remove useless code 2022-05-16 17:29:08 +08:00
MetaCubeX
aa0d174ccb fix: strategyStickySessions nil pointer 2022-05-16 17:06:44 +08:00
adlyq
b8e9c3d55a fix: geoip ReverseMatch 2022-05-16 17:06:44 +08:00
adlyq
0b4c498c93 refactor: new way to get interface for android 2022-05-16 17:06:44 +08:00
adlyq
efc7c82cac feat: "!"(not) support for geosite
eg. GEOSITE,!CN,Proxy & dns.fallback-filter.geosite: ['!CN']
2022-05-15 13:16:45 +08:00
28 changed files with 233 additions and 217 deletions

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/gofrs/uuid"
"net" "net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
@@ -17,6 +18,7 @@ type Base struct {
tp C.AdapterType tp C.AdapterType
udp bool udp bool
rmark int rmark int
id string
} }
// Name implements C.ProxyAdapter // Name implements C.ProxyAdapter
@@ -24,6 +26,20 @@ func (b *Base) Name() string {
return b.name return b.name
} }
// Id implements C.ProxyAdapter
func (b *Base) Id() string {
if b.id == "" {
id, err := uuid.NewV6()
if err != nil {
b.id = b.name
} else {
b.id = id.String()
}
}
return b.id
}
// Type implements C.ProxyAdapter // Type implements C.ProxyAdapter
func (b *Base) Type() C.AdapterType { func (b *Base) Type() C.AdapterType {
return b.tp return b.tp
@@ -58,6 +74,7 @@ func (b *Base) SupportUDP() bool {
func (b *Base) MarshalJSON() ([]byte, error) { func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{ return json.Marshal(map[string]string{
"type": b.Type().String(), "type": b.Type().String(),
"id": b.Id(),
}) })
} }

View File

@@ -39,9 +39,6 @@ func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata
pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...) pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
pc.AppendToChains(f) pc.AppendToChains(f)
f.onDialSuccess()
} else {
f.onDialFailed()
} }
return pc, err return pc, err

View File

@@ -19,8 +19,10 @@ type GroupBase struct {
providers []provider.ProxyProvider providers []provider.ProxyProvider
versions sync.Map // map[string]uint versions sync.Map // map[string]uint
proxies sync.Map // map[string][]C.Proxy proxies sync.Map // map[string][]C.Proxy
failedTimes *atomic.Int32 failedTestMux sync.Mutex
failedTime *atomic.Int64 failedTimes int
failedTime time.Time
failedTesting *atomic.Bool
} }
type GroupBaseOption struct { type GroupBaseOption struct {
@@ -38,8 +40,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
Base: outbound.NewBase(opt.BaseOption), Base: outbound.NewBase(opt.BaseOption),
filter: filter, filter: filter,
providers: opt.providers, providers: opt.providers,
failedTimes: atomic.NewInt32(-1), failedTesting: atomic.NewBool(false),
failedTime: atomic.NewInt64(-1),
} }
} }
@@ -105,29 +106,43 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
} }
func (gb *GroupBase) onDialFailed() { func (gb *GroupBase) onDialFailed() {
if gb.failedTime.Load() == -1 { if gb.failedTesting.Load() {
log.Warnln("%s first failed", gb.Name()) return
now := time.Now().UnixMilli()
gb.failedTime.Store(now)
gb.failedTimes.Store(1)
} else {
if gb.failedTime.Load()-time.Now().UnixMilli() > gb.failedIntervalTime() {
gb.failedTimes.Store(-1)
gb.failedTime.Store(-1)
} else {
failedCount := gb.failedTimes.Inc()
log.Warnln("%s failed count: %d", gb.Name(), failedCount)
if failedCount >= gb.maxFailedTimes() {
log.Warnln("because %s failed multiple times, active health check", gb.Name())
for _, proxyProvider := range gb.providers {
go proxyProvider.HealthCheck()
} }
gb.failedTimes.Store(-1) go func() {
gb.failedTime.Store(-1) gb.failedTestMux.Lock()
} defer gb.failedTestMux.Unlock()
gb.failedTimes++
if gb.failedTimes == 1 {
log.Warnln("ProxyGroup: %s first failed", gb.Name())
gb.failedTime = time.Now()
} else {
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() {
return
}
log.Warnln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
if gb.failedTimes >= gb.maxFailedTimes() {
gb.failedTesting.Store(true)
log.Warnln("because %s failed multiple times, active health check", gb.Name())
wg := sync.WaitGroup{}
for _, proxyProvider := range gb.providers {
wg.Add(1)
proxyProvider := proxyProvider
go func() {
defer wg.Done()
proxyProvider.HealthCheck()
}()
}
wg.Wait()
gb.failedTesting.Store(false)
gb.failedTimes = 0
} }
} }
}()
} }
func (gb *GroupBase) failedIntervalTime() int64 { func (gb *GroupBase) failedIntervalTime() int64 {
@@ -135,10 +150,15 @@ func (gb *GroupBase) failedIntervalTime() int64 {
} }
func (gb *GroupBase) onDialSuccess() { func (gb *GroupBase) onDialSuccess() {
gb.failedTimes.Store(-1) if !gb.failedTesting.Load() {
gb.failedTime.Store(-1) gb.failedTimes = 0
}
} }
func (gb *GroupBase) maxFailedTimes() int32 { func (gb *GroupBase) maxFailedTimes() int {
return 5 return 5
} }
func (gb *GroupBase) failedTimeoutInterval() time.Duration {
return 5 * time.Second
}

View File

@@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/rand" "github.com/Dreamacro/clash/common/cache"
"net" "net"
"time" "time"
@@ -60,6 +60,16 @@ func getKey(metadata *C.Metadata) string {
return metadata.DstIP.String() return metadata.DstIP.String()
} }
func getKeyWithSrcAndDst(metadata *C.Metadata) string {
dst := getKey(metadata)
src := ""
if metadata != nil {
src = metadata.SrcIP.String()
}
return fmt.Sprintf("%s%s", src, dst)
}
func jumpHash(key uint64, buckets int32) int32 { func jumpHash(key uint64, buckets int32) int32 {
var b, j int64 var b, j int64
@@ -140,56 +150,31 @@ func strategyConsistentHashing() strategyFn {
} }
func strategyStickySessions() strategyFn { func strategyStickySessions() strategyFn {
timeout := int64(600) ttl := time.Minute * 10
type Session struct {
idx int c := cache.New[uint64, int](1 * time.Second)
time time.Time return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
} key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
Sessions := make(map[string]map[string]Session) length := len(proxies)
go func() { idx, expireTime := c.GetWithExpire(key)
for true { if expireTime.IsZero() {
time.Sleep(time.Second * 60) idx = int(jumpHash(key+uint64(time.Now().UnixMilli()), int32(length)))
now := time.Now().Unix() }
for _, subMap := range Sessions {
for dest, session := range subMap {
if now-session.time.Unix() > timeout {
delete(subMap, dest)
}
}
}
}
}()
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
src := metadata.SrcIP.String()
dest := getKey(metadata)
now := time.Now()
length := len(proxies)
if Sessions[src] == nil {
Sessions[src] = make(map[string]Session)
}
session, ok := Sessions[src][dest]
if !ok || now.Unix()-session.time.Unix() > timeout {
session.idx = rand.Intn(length)
}
session.time = now
var i int
var res C.Proxy
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
idx := (session.idx + i) % length nowIdx := (idx + 1) % length
proxy := proxies[idx] proxy := proxies[nowIdx]
if proxy.Alive() { if proxy.Alive() {
session.idx = idx if nowIdx != idx {
res = proxy c.Put(key, idx, -1)
break c.Put(key, nowIdx, ttl)
}
return proxy
} }
} }
if i == length {
session.idx = 0 return proxies[0]
res = proxies[0]
}
Sessions[src][dest] = session
return res
} }
} }

View File

@@ -37,8 +37,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
c.AppendToChains(u) c.AppendToChains(u)
u.failedTimes.Store(-1) u.onDialSuccess()
u.failedTime.Store(-1)
} else { } else {
u.onDialFailed() u.onDialFailed()
} }
@@ -50,9 +49,6 @@ func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...) pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
pc.AppendToChains(u) pc.AppendToChains(u)
u.onDialSuccess()
} else {
u.onDialFailed()
} }
return pc, err return pc, err

View File

@@ -33,9 +33,10 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
type DomainMatcher struct { type DomainMatcher struct {
matchers strmatcher.IndexMatcher matchers strmatcher.IndexMatcher
not bool
} }
func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) { func NewMphMatcherGroup(domains []*Domain, not bool) (*DomainMatcher, error) {
g := strmatcher.NewMphMatcherGroup() g := strmatcher.NewMphMatcherGroup()
for _, d := range domains { for _, d := range domains {
matcherType, f := matcherTypeMap[d.Type] matcherType, f := matcherTypeMap[d.Type]
@@ -50,11 +51,12 @@ func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
g.Build() g.Build()
return &DomainMatcher{ return &DomainMatcher{
matchers: g, matchers: g,
not: not,
}, nil }, nil
} }
// NewDomainMatcher new domain matcher. // NewDomainMatcher new domain matcher.
func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) { func NewDomainMatcher(domains []*Domain, not bool) (*DomainMatcher, error) {
g := new(strmatcher.MatcherGroup) g := new(strmatcher.MatcherGroup)
for _, d := range domains { for _, d := range domains {
m, err := domainToMatcher(d) m, err := domainToMatcher(d)
@@ -66,11 +68,16 @@ func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
return &DomainMatcher{ return &DomainMatcher{
matchers: g, matchers: g,
not: not,
}, nil }, nil
} }
func (m *DomainMatcher) ApplyDomain(domain string) bool { func (m *DomainMatcher) ApplyDomain(domain string) bool {
return len(m.matchers.Match(strings.ToLower(domain))) > 0 isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0
if m.not {
isMatched = !isMatched
}
return isMatched
} }
// CIDRList is an alias of []*CIDR to provide sort.Interface. // CIDRList is an alias of []*CIDR to provide sort.Interface.

View File

@@ -1,9 +1,9 @@
package geodata package geodata
import ( import (
"fmt"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"strings"
) )
var geoLoaderName = "memconservative" var geoLoaderName = "memconservative"
@@ -35,6 +35,16 @@ func Verify(name string) bool {
} }
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) { func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
if len(countryCode) == 0 {
return nil, 0, fmt.Errorf("country code could not be empty")
}
not := false
if countryCode[0] == '!' {
not = true
countryCode = countryCode[1:]
}
geoLoader, err := GetGeoDataLoader(geoLoaderName) geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@@ -50,7 +60,7 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
matcher, err := router.NewDomainMatcher(domains) matcher, err := router.NewDomainMatcher(domains)
mphminimal perfect hash algorithm mphminimal perfect hash algorithm
*/ */
matcher, err := router.NewMphMatcherGroup(domains) matcher, err := router.NewMphMatcherGroup(domains, not)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@@ -59,12 +69,21 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
} }
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) { func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
if len(country) == 0 {
return nil, 0, fmt.Errorf("country code could not be empty")
}
geoLoader, err := GetGeoDataLoader(geoLoaderName) geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
records, err := geoLoader.LoadGeoIP(strings.ReplaceAll(country, "!", "")) not := false
if country[0] == '!' {
not = true
country = country[1:]
}
records, err := geoLoader.LoadGeoIP(country)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@@ -72,7 +91,7 @@ func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
geoIP := &router.GeoIP{ geoIP := &router.GeoIP{
CountryCode: country, CountryCode: country,
Cidr: records, Cidr: records,
ReverseMatch: strings.Contains(country, "!"), ReverseMatch: not,
} }
matcher, err := router.NewGeoIPMatcher(geoIP) matcher, err := router.NewGeoIPMatcher(geoIP)

View File

@@ -117,13 +117,13 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta
host, err := sniffer.SniffTCP(bytes) host, err := sniffer.SniffTCP(bytes)
if err != nil { if err != nil {
log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP) //log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP)
continue continue
} }
_, err = netip.ParseAddr(host) _, err = netip.ParseAddr(host)
if err == nil { if err == nil {
log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP) //log.Debugln("[Sniffer] [%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP)
continue continue
} }

View File

@@ -99,12 +99,6 @@ type FallbackFilter struct {
GeoSite []*router.DomainMatcher `yaml:"geosite"` GeoSite []*router.DomainMatcher `yaml:"geosite"`
} }
var (
GroupsList = list.New()
ProxiesList = list.New()
ParsingProxiesCallback func(groupsList *list.List, proxiesList *list.List)
)
// Profile config // Profile config
type Profile struct { type Profile struct {
StoreSelected bool `yaml:"store-selected"` StoreSelected bool `yaml:"store-selected"`
@@ -130,7 +124,6 @@ type IPTables struct {
type Sniffer struct { type Sniffer struct {
Enable bool Enable bool
Force bool
Sniffers []sniffer.Type Sniffers []sniffer.Type
Reverses *trie.DomainTrie[bool] Reverses *trie.DomainTrie[bool]
ForceDomain *trie.DomainTrie[bool] ForceDomain *trie.DomainTrie[bool]
@@ -213,7 +206,7 @@ type RawConfig struct {
GeodataLoader string `yaml:"geodata-loader"` GeodataLoader string `yaml:"geodata-loader"`
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
Sniffer SnifferRaw `yaml:"sniffer"` Sniffer RawSniffer `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
RuleProvider map[string]map[string]any `yaml:"rule-providers"` RuleProvider map[string]map[string]any `yaml:"rule-providers"`
Hosts map[string]string `yaml:"hosts"` Hosts map[string]string `yaml:"hosts"`
@@ -227,14 +220,11 @@ type RawConfig struct {
Rule []string `yaml:"rules"` Rule []string `yaml:"rules"`
} }
type SnifferRaw struct { type RawSniffer struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Sniffing []string `yaml:"sniffing" json:"sniffing"` Sniffing []string `yaml:"sniffing" json:"sniffing"`
Force bool `yaml:"force" json:"force"`
Reverse []string `yaml:"reverses" json:"reverses"`
ForceDomain []string `yaml:"force-domain" json:"force-domain"` ForceDomain []string `yaml:"force-domain" json:"force-domain"`
SkipDomain []string `yaml:"skip-domain" json:"skip-domain"` SkipDomain []string `yaml:"skip-domain" json:"skip-domain"`
SkipSNI []string `yaml:"skip-sni" json:"skip-sni"`
Ports []string `yaml:"port-whitelist" json:"port-whitelist"` Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
} }
@@ -304,11 +294,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
"www.msftconnecttest.com", "www.msftconnecttest.com",
}, },
}, },
Sniffer: SnifferRaw{ Sniffer: RawSniffer{
Enable: false, Enable: false,
Force: false,
Sniffing: []string{}, Sniffing: []string{},
Reverse: []string{},
ForceDomain: []string{}, ForceDomain: []string{},
SkipDomain: []string{}, SkipDomain: []string{},
Ports: []string{}, Ports: []string{},
@@ -432,8 +420,8 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
providersConfig := cfg.ProxyProvider providersConfig := cfg.ProxyProvider
var proxyList []string var proxyList []string
_proxiesList := list.New() proxiesList := list.New()
_groupsList := list.New() groupsList := list.New()
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect()) proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
@@ -453,7 +441,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
} }
proxies[proxy.Name()] = proxy proxies[proxy.Name()] = proxy
proxyList = append(proxyList, proxy.Name()) proxyList = append(proxyList, proxy.Name())
_proxiesList.PushBack(mapping) proxiesList.PushBack(mapping)
} }
// keep the original order of ProxyGroups in config file // keep the original order of ProxyGroups in config file
@@ -463,7 +451,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
return nil, nil, fmt.Errorf("proxy group %d: missing name", idx) return nil, nil, fmt.Errorf("proxy group %d: missing name", idx)
} }
proxyList = append(proxyList, groupName) proxyList = append(proxyList, groupName)
_groupsList.PushBack(mapping) groupsList.PushBack(mapping)
} }
// check if any loop exists and sort the ProxyGroups // check if any loop exists and sort the ProxyGroups
@@ -518,12 +506,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
[]providerTypes.ProxyProvider{pd}, []providerTypes.ProxyProvider{pd},
) )
proxies["GLOBAL"] = adapter.NewProxy(global) proxies["GLOBAL"] = adapter.NewProxy(global)
ProxiesList = _proxiesList
GroupsList = _groupsList
if ParsingProxiesCallback != nil {
// refresh tray menu
go ParsingProxiesCallback(GroupsList, ProxiesList)
}
return proxies, providersMap, nil return proxies, providersMap, nil
} }
@@ -919,10 +902,9 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
}, nil }, nil
} }
func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) { func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
sniffer := &Sniffer{ sniffer := &Sniffer{
Enable: snifferRaw.Enable, Enable: snifferRaw.Enable,
Force: snifferRaw.Force,
} }
var ports []utils.Range[uint16] var ports []utils.Range[uint16]
@@ -979,10 +961,7 @@ func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err) return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
} }
} }
if snifferRaw.SkipSNI != nil {
log.Warnln("Sniffer param skip-sni renamed to ship-domain, old param will be removed in the release version")
snifferRaw.SkipDomain = snifferRaw.SkipSNI
}
sniffer.SkipDomain = trie.New[bool]() sniffer.SkipDomain = trie.New[bool]()
for _, domain := range snifferRaw.SkipDomain { for _, domain := range snifferRaw.SkipDomain {
err := sniffer.SkipDomain.Insert(domain, true) err := sniffer.SkipDomain.Insert(domain, true)
@@ -991,27 +970,5 @@ func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
} }
} }
// Compatibility, remove it when release
if strings.Contains(C.Version, "alpha") || strings.Contains(C.Version, "develop") || strings.Contains(C.Version, "1.10.0") {
log.Warnln("Sniffer param force and reverses deprecated, will be removed in the release version, see https://github.com/MetaCubeX/Clash.Meta/commit/48a01adb7a4f38974b9d9639f931d0d245aebf28")
if snifferRaw.Force {
// match all domain
sniffer.ForceDomain.Insert("+", true)
for _, domain := range snifferRaw.Reverse {
err := sniffer.SkipDomain.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s], error:%v", domain, err)
}
}
} else {
for _, domain := range snifferRaw.Reverse {
err := sniffer.ForceDomain.Insert(domain, true)
if err != nil {
return nil, fmt.Errorf("error domian[%s], error:%v", domain, err)
}
}
}
}
return sniffer, nil return sniffer, nil
} }

View File

@@ -89,9 +89,6 @@ type Metadata struct {
RemoteDst string `json:"remoteDestination"` RemoteDst string `json:"remoteDestination"`
} }
// avoid stack overflow
type jsonMetadata Metadata
func (m *Metadata) RemoteAddress() string { func (m *Metadata) RemoteAddress() string {
return net.JoinHostPort(m.String(), m.DstPort) return net.JoinHostPort(m.String(), m.DstPort)
} }

View File

@@ -46,3 +46,11 @@ func (re *RuleExtra) NotMatchProcessName(processName string) bool {
type RuleGeoSite interface { type RuleGeoSite interface {
GetDomainMatcher() *router.DomainMatcher GetDomainMatcher() *router.DomainMatcher
} }
type RuleGeoIP interface {
GetIPMatcher() *router.GeoIPMatcher
}
type RuleGroup interface {
GetRecodeSize() int
}

View File

@@ -177,7 +177,7 @@ func (dc *quicClient) openSession() (quic.Connection, error) {
return nil, fmt.Errorf("quio create packet failed") return nil, fmt.Errorf("quio create packet failed")
} }
udp = wrapConn.PacketConn udp = wrapConn
} }
session, err := quic.Dial(udp, &udpAddr, host, tlsConfig, quicConfig) session, err := quic.Dial(udp, &udpAddr, host, tlsConfig, quicConfig)

View File

@@ -164,6 +164,7 @@ func withResolver(resolver *Resolver) handler {
msg.SetRcode(r, msg.Rcode) msg.SetRcode(r, msg.Rcode)
msg.Authoritative = true msg.Authoritative = true
log.Debugln("[DNS] %s --> %s", msgToDomain(r), msgToIP(msg))
return msg, nil return msg, nil
} }
} }

View File

@@ -7,7 +7,6 @@ import (
"go.uber.org/atomic" "go.uber.org/atomic"
"math/rand" "math/rand"
"net/netip" "net/netip"
"strings"
"time" "time"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
@@ -232,7 +231,7 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
return nil return nil
} }
domain := r.msgToDomain(m) domain := msgToDomain(m)
if domain == "" { if domain == "" {
return nil return nil
} }
@@ -251,7 +250,7 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
return false return false
} }
domain := r.msgToDomain(m) domain := msgToDomain(m)
if domain == "" { if domain == "" {
return false return false
@@ -332,14 +331,6 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ips []netip.Addr, err
return return
} }
func (r *Resolver) msgToDomain(msg *D.Msg) string {
if len(msg.Question) > 0 {
return strings.TrimRight(msg.Question[0].Name, ".")
}
return ""
}
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result { func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
ch := make(chan *result, 1) ch := make(chan *result, 1)
go func() { go func() {

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"strings"
"time" "time"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
@@ -116,6 +117,14 @@ func msgToIP(msg *D.Msg) []netip.Addr {
return ips return ips
} }
func msgToDomain(msg *D.Msg) string {
if len(msg.Question) > 0 {
return strings.TrimRight(msg.Question[0].Name, ".")
}
return ""
}
type wrapPacketConn struct { type wrapPacketConn struct {
net.PacketConn net.PacketConn
rAddr net.Addr rAddr net.Addr

View File

@@ -79,7 +79,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateSniffer(cfg.Sniffer) updateSniffer(cfg.Sniffer)
updateHosts(cfg.Hosts) updateHosts(cfg.Hosts)
initInnerTcp() initInnerTcp()
updateDNS(cfg.DNS) updateDNS(cfg.DNS, cfg.General.IPv6)
loadProxyProvider(cfg.Providers) loadProxyProvider(cfg.Providers)
updateProfile(cfg) updateProfile(cfg)
loadRuleProvider(cfg.RuleProviders) loadRuleProvider(cfg.RuleProviders)
@@ -125,13 +125,16 @@ func GetGeneral() *config.General {
func updateExperimental(c *config.Config) {} func updateExperimental(c *config.Config) {}
func updateDNS(c *config.DNS) { func updateDNS(c *config.DNS, generalIPv6 bool) {
if !c.Enable { if !c.Enable {
resolver.DisableIPv6 = !generalIPv6
resolver.DefaultResolver = nil resolver.DefaultResolver = nil
resolver.DefaultHostMapper = nil resolver.DefaultHostMapper = nil
resolver.DefaultLocalServer = nil resolver.DefaultLocalServer = nil
dns.ReCreateServer("", nil, nil) dns.ReCreateServer("", nil, nil)
return return
} else {
resolver.DisableIPv6 = !c.IPv6
} }
cfg := dns.Config{ cfg := dns.Config{
@@ -153,8 +156,6 @@ func updateDNS(c *config.DNS) {
ProxyServer: c.ProxyServerNameserver, ProxyServer: c.ProxyServerNameserver,
} }
resolver.DisableIPv6 = !cfg.IPv6
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)
pr := dns.NewProxyServerHostResolver(r) pr := dns.NewProxyServerHostResolver(r)
m := dns.NewEnhancer(cfg) m := dns.NewEnhancer(cfg)

View File

@@ -1,6 +1,7 @@
package route package route
import ( import (
"github.com/Dreamacro/clash/constant"
"net/http" "net/http"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
@@ -19,17 +20,23 @@ type Rule struct {
Type string `json:"type"` Type string `json:"type"`
Payload string `json:"payload"` Payload string `json:"payload"`
Proxy string `json:"proxy"` Proxy string `json:"proxy"`
Size int `json:"Size"`
} }
func getRules(w http.ResponseWriter, r *http.Request) { func getRules(w http.ResponseWriter, r *http.Request) {
rawRules := tunnel.Rules() rawRules := tunnel.Rules()
rules := []Rule{} rules := []Rule{}
for _, rule := range rawRules { for _, rule := range rawRules {
rules = append(rules, Rule{ r := Rule{
Type: rule.RuleType().String(), Type: rule.RuleType().String(),
Payload: rule.Payload(), Payload: rule.Payload(),
Proxy: rule.Adapter(), Proxy: rule.Adapter(),
}) Size: -1,
}
if rule.RuleType() == constant.GEOIP || rule.RuleType() == constant.GEOSITE {
r.Size = rule.(constant.RuleGroup).GetRecodeSize()
}
rules = append(rules, r)
} }

View File

@@ -33,7 +33,7 @@ func DefaultInterfaceChangeMonitor() {
interfaceName, err := GetAutoDetectInterface() interfaceName, err := GetAutoDetectInterface()
if err != nil { if err != nil {
log.Warnln("[TUN] default interface monitor exited, cause: %v", err) log.Warnln("[TUN] default interface monitor exited, cause: %v", err)
break continue
} }
old := dialer.DefaultInterface.Load() old := dialer.DefaultInterface.Load()

View File

@@ -10,18 +10,18 @@ import (
"strings" "strings"
) )
func GetAutoDetectInterface() (string, error) { func GetAutoDetectInterface() (ifn string, err error) {
res, err := cmd.ExecCmd("sh -c ip route | awk '{print $3}' | xargs echo -n") cmdRes, err := cmd.ExecCmd("ip route get 1.1.1.1 uid 4294967295")
if err != nil {
return "", err sps := strings.Split(cmdRes, " ")
if len(sps) > 4 {
ifn = sps[4]
} }
ifaces := strings.Split(res, " ")
for _, iface := range ifaces { if ifn == "" {
if iface == "wlan0" { err = fmt.Errorf("interface not found")
return "wlan0", nil
} }
} return
return ifaces[0], nil
} }
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute, autoDetectInterface bool) error { func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute, autoDetectInterface bool) error {
@@ -40,6 +40,10 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int,
return err return err
} }
if err = execRouterCmd("add", addr.Masked().String(), interfaceName, ip.String(), "main"); err != nil {
return err
}
if autoRoute { if autoRoute {
err = configInterfaceRouting(interfaceName, addr, autoDetectInterface) err = configInterfaceRouting(interfaceName, addr, autoDetectInterface)
} }

View File

@@ -12,7 +12,6 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons" D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter" "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
@@ -39,8 +38,6 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) { if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
go func() { go func() {
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
defer func() { defer func() {
_ = pool.Put(buf) _ = pool.Put(buf)
@@ -123,8 +120,6 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
} }
_, _ = tunConn.WriteTo(msg, addr) _, _ = tunConn.WriteTo(msg, addr)
log.Debugln("[TUN] hijack dns udp: %s", rAddr.String())
}() }()
continue continue

View File

@@ -93,8 +93,6 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
if D.ShouldHijackDns(dnsAddr, rAddrPort) { if D.ShouldHijackDns(dnsAddr, rAddrPort) {
go func() { go func() {
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
buf := pool.Get(pool.UDPBufferSize) buf := pool.Get(pool.UDPBufferSize)
defer func() { defer func() {
_ = pool.Put(buf) _ = pool.Put(buf)
@@ -186,8 +184,6 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
_, _ = stack.UDP().WriteTo(msg, rAddr, lAddr) _, _ = stack.UDP().WriteTo(msg, rAddr, lAddr)
_ = pool.Put(buf) _ = pool.Put(buf)
log.Debugln("[TUN] hijack dns udp: %s", rAddrPort.String())
}() }()
continue continue

View File

@@ -18,6 +18,7 @@ type GEOIP struct {
adapter string adapter string
noResolveIP bool noResolveIP bool
geoIPMatcher *router.GeoIPMatcher geoIPMatcher *router.GeoIPMatcher
recodeSize int
} }
func (g *GEOIP) RuleType() C.RuleType { func (g *GEOIP) RuleType() C.RuleType {
@@ -65,6 +66,10 @@ func (g *GEOIP) GetIPMatcher() *router.GeoIPMatcher {
return g.geoIPMatcher return g.geoIPMatcher
} }
func (g *GEOIP) GetRecodeSize() int {
return g.recodeSize
}
func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) { func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) {
if !C.GeodataMode { if !C.GeodataMode {
geoip := &GEOIP{ geoip := &GEOIP{
@@ -76,18 +81,19 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error)
return geoip, nil return geoip, nil
} }
geoIPMatcher, recordsCount, err := geodata.LoadGeoIPMatcher(country) geoIPMatcher, size, err := geodata.LoadGeoIPMatcher(country)
if err != nil { if err != nil {
return nil, fmt.Errorf("[GeoIP] %s", err.Error()) return nil, fmt.Errorf("[GeoIP] %s", err.Error())
} }
log.Infoln("Start initial GeoIP rule %s => %s, records: %d", country, adapter, recordsCount) log.Infoln("Start initial GeoIP rule %s => %s, records: %d", country, adapter, size)
geoip := &GEOIP{ geoip := &GEOIP{
Base: &Base{}, Base: &Base{},
country: country, country: country,
adapter: adapter, adapter: adapter,
noResolveIP: noResolveIP, noResolveIP: noResolveIP,
geoIPMatcher: geoIPMatcher, geoIPMatcher: geoIPMatcher,
recodeSize: size,
} }
return geoip, nil return geoip, nil
} }

View File

@@ -17,6 +17,7 @@ type GEOSITE struct {
country string country string
adapter string adapter string
matcher *router.DomainMatcher matcher *router.DomainMatcher
recodeSize int
} }
func (gs *GEOSITE) RuleType() C.RuleType { func (gs *GEOSITE) RuleType() C.RuleType {
@@ -44,19 +45,24 @@ func (gs *GEOSITE) GetDomainMatcher() *router.DomainMatcher {
return gs.matcher return gs.matcher
} }
func (gs *GEOSITE) GetRecodeSize() int {
return gs.recodeSize
}
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country) matcher, size, err := geodata.LoadGeoSiteMatcher(country)
if err != nil { if err != nil {
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error()) return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
} }
log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, recordsCount) log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, size)
geoSite := &GEOSITE{ geoSite := &GEOSITE{
Base: &Base{}, Base: &Base{},
country: country, country: country,
adapter: adapter, adapter: adapter,
matcher: matcher, matcher: matcher,
recodeSize: size,
} }
return geoSite, nil return geoSite, nil

View File

@@ -102,7 +102,8 @@ func parseRule(tp, payload string, params []string) (C.Rule, error) {
case "PROCESS-PATH": case "PROCESS-PATH":
parsed, parseErr = RC.NewProcess(payload, "", false) parsed, parseErr = RC.NewProcess(payload, "", false)
case "RULE-SET": case "RULE-SET":
parsed, parseErr = provider.NewRuleSet(payload, "") noResolve := RC.HasNoResolve(params)
parsed, parseErr = provider.NewRuleSet(payload, "", noResolve)
case "NOT": case "NOT":
parsed, parseErr = NewNOT(payload, "") parsed, parseErr = NewNOT(payload, "")
case "AND": case "AND":

View File

@@ -50,7 +50,8 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
case "NOT": case "NOT":
parsed, parseErr = logic.NewNOT(payload, target) parsed, parseErr = logic.NewNOT(payload, target)
case "RULE-SET": case "RULE-SET":
parsed, parseErr = RP.NewRuleSet(payload, target) noResolve := RC.HasNoResolve(params)
parsed, parseErr = RP.NewRuleSet(payload, target, noResolve)
case "MATCH": case "MATCH":
parsed = RC.NewMatch(target) parsed = RC.NewMatch(target)
default: default:

View File

@@ -30,26 +30,20 @@ func (c *classicalStrategy) ShouldResolveIP() bool {
} }
func (c *classicalStrategy) OnUpdate(rules []string) { func (c *classicalStrategy) OnUpdate(rules []string) {
var classicalRules []C.Rule
shouldResolveIP := false
count := 0
for _, rawRule := range rules { for _, rawRule := range rules {
ruleType, rule, params := ruleParse(rawRule) ruleType, rule, params := ruleParse(rawRule)
r, err := parseRule(ruleType, rule, "", params) r, err := parseRule(ruleType, rule, "", params)
if err != nil { if err != nil {
log.Warnln("parse rule error:[%s]", err.Error()) log.Warnln("parse rule error:[%s]", err.Error())
} else { } else {
if !shouldResolveIP { if !c.shouldResolveIP {
shouldResolveIP = shouldResolveIP || r.ShouldResolveIP() c.shouldResolveIP = r.ShouldResolveIP()
} }
classicalRules = append(classicalRules, r) c.rules = append(c.rules, r)
count++ c.count++
} }
} }
c.rules = classicalRules
c.count = count
} }
func NewClassicalStrategy() *classicalStrategy { func NewClassicalStrategy() *classicalStrategy {

View File

@@ -8,7 +8,6 @@ import (
) )
type domainStrategy struct { type domainStrategy struct {
shouldResolveIP bool
count int count int
domainRules *trie.DomainTrie[bool] domainRules *trie.DomainTrie[bool]
} }
@@ -22,7 +21,7 @@ func (d *domainStrategy) Count() int {
} }
func (d *domainStrategy) ShouldResolveIP() bool { func (d *domainStrategy) ShouldResolveIP() bool {
return d.shouldResolveIP return false
} }
func (d *domainStrategy) OnUpdate(rules []string) { func (d *domainStrategy) OnUpdate(rules []string) {
@@ -55,5 +54,5 @@ func ruleParse(ruleRaw string) (string, string, []string) {
} }
func NewDomainStrategy() *domainStrategy { func NewDomainStrategy() *domainStrategy {
return &domainStrategy{shouldResolveIP: false} return &domainStrategy{}
} }

View File

@@ -12,6 +12,7 @@ type RuleSet struct {
ruleProviderName string ruleProviderName string
adapter string adapter string
ruleProvider P.RuleProvider ruleProvider P.RuleProvider
noResolveIP bool
} }
func (rs *RuleSet) ShouldFindProcess() bool { func (rs *RuleSet) ShouldFindProcess() bool {
@@ -35,7 +36,7 @@ func (rs *RuleSet) Payload() string {
} }
func (rs *RuleSet) ShouldResolveIP() bool { func (rs *RuleSet) ShouldResolveIP() bool {
return rs.getProviders().ShouldResolveIP() return !rs.noResolveIP && rs.getProviders().ShouldResolveIP()
} }
func (rs *RuleSet) getProviders() P.RuleProvider { func (rs *RuleSet) getProviders() P.RuleProvider {
if rs.ruleProvider == nil { if rs.ruleProvider == nil {
@@ -46,7 +47,7 @@ func (rs *RuleSet) getProviders() P.RuleProvider {
return rs.ruleProvider return rs.ruleProvider
} }
func NewRuleSet(ruleProviderName string, adapter string) (*RuleSet, error) { func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool) (*RuleSet, error) {
rp, ok := RuleProviders()[ruleProviderName] rp, ok := RuleProviders()[ruleProviderName]
if !ok { if !ok {
return nil, fmt.Errorf("rule set %s not found", ruleProviderName) return nil, fmt.Errorf("rule set %s not found", ruleProviderName)
@@ -56,5 +57,6 @@ func NewRuleSet(ruleProviderName string, adapter string) (*RuleSet, error) {
ruleProviderName: ruleProviderName, ruleProviderName: ruleProviderName,
adapter: adapter, adapter: adapter,
ruleProvider: rp, ruleProvider: rp,
noResolveIP: noResolveIP,
}, nil }, nil
} }