Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b09db5f7f | ||
|
|
b5623602f5 | ||
|
|
16b27b3a1f | ||
|
|
8b00be9039 | ||
|
|
fa9e27c5e4 | ||
|
|
f4d9384603 | ||
|
|
c4408612b3 | ||
|
|
0742f7db26 | ||
|
|
891c2fe899 | ||
|
|
b831eb178b | ||
|
|
962ceaa89e | ||
|
|
d52b00bd34 | ||
|
|
aa0d174ccb | ||
|
|
b8e9c3d55a | ||
|
|
0b4c498c93 | ||
|
|
efc7c82cac |
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gofrs/uuid"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
@@ -17,6 +18,7 @@ type Base struct {
|
||||
tp C.AdapterType
|
||||
udp bool
|
||||
rmark int
|
||||
id string
|
||||
}
|
||||
|
||||
// Name implements C.ProxyAdapter
|
||||
@@ -24,6 +26,20 @@ func (b *Base) Name() string {
|
||||
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
|
||||
func (b *Base) Type() C.AdapterType {
|
||||
return b.tp
|
||||
@@ -58,6 +74,7 @@ func (b *Base) SupportUDP() bool {
|
||||
func (b *Base) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]string{
|
||||
"type": b.Type().String(),
|
||||
"id": b.Id(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -39,9 +39,6 @@ func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata
|
||||
pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
|
||||
if err == nil {
|
||||
pc.AppendToChains(f)
|
||||
f.onDialSuccess()
|
||||
} else {
|
||||
f.onDialFailed()
|
||||
}
|
||||
|
||||
return pc, err
|
||||
|
||||
@@ -15,12 +15,14 @@ import (
|
||||
|
||||
type GroupBase struct {
|
||||
*outbound.Base
|
||||
filter *regexp2.Regexp
|
||||
providers []provider.ProxyProvider
|
||||
versions sync.Map // map[string]uint
|
||||
proxies sync.Map // map[string][]C.Proxy
|
||||
failedTimes *atomic.Int32
|
||||
failedTime *atomic.Int64
|
||||
filter *regexp2.Regexp
|
||||
providers []provider.ProxyProvider
|
||||
versions sync.Map // map[string]uint
|
||||
proxies sync.Map // map[string][]C.Proxy
|
||||
failedTestMux sync.Mutex
|
||||
failedTimes int
|
||||
failedTime time.Time
|
||||
failedTesting *atomic.Bool
|
||||
}
|
||||
|
||||
type GroupBaseOption struct {
|
||||
@@ -35,11 +37,10 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
filter = regexp2.MustCompile(opt.filter, 0)
|
||||
}
|
||||
return &GroupBase{
|
||||
Base: outbound.NewBase(opt.BaseOption),
|
||||
filter: filter,
|
||||
providers: opt.providers,
|
||||
failedTimes: atomic.NewInt32(-1),
|
||||
failedTime: atomic.NewInt64(-1),
|
||||
Base: outbound.NewBase(opt.BaseOption),
|
||||
filter: filter,
|
||||
providers: opt.providers,
|
||||
failedTesting: atomic.NewBool(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,29 +106,43 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
}
|
||||
|
||||
func (gb *GroupBase) onDialFailed() {
|
||||
if gb.failedTime.Load() == -1 {
|
||||
log.Warnln("%s first failed", gb.Name())
|
||||
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)
|
||||
if gb.failedTesting.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
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 {
|
||||
failedCount := gb.failedTimes.Inc()
|
||||
log.Warnln("%s failed count: %d", gb.Name(), failedCount)
|
||||
if failedCount >= gb.maxFailedTimes() {
|
||||
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 {
|
||||
go proxyProvider.HealthCheck()
|
||||
wg.Add(1)
|
||||
proxyProvider := proxyProvider
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
proxyProvider.HealthCheck()
|
||||
}()
|
||||
}
|
||||
|
||||
gb.failedTimes.Store(-1)
|
||||
gb.failedTime.Store(-1)
|
||||
wg.Wait()
|
||||
gb.failedTesting.Store(false)
|
||||
gb.failedTimes = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (gb *GroupBase) failedIntervalTime() int64 {
|
||||
@@ -135,10 +150,15 @@ func (gb *GroupBase) failedIntervalTime() int64 {
|
||||
}
|
||||
|
||||
func (gb *GroupBase) onDialSuccess() {
|
||||
gb.failedTimes.Store(-1)
|
||||
gb.failedTime.Store(-1)
|
||||
if !gb.failedTesting.Load() {
|
||||
gb.failedTimes = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GroupBase) maxFailedTimes() int32 {
|
||||
func (gb *GroupBase) maxFailedTimes() int {
|
||||
return 5
|
||||
}
|
||||
|
||||
func (gb *GroupBase) failedTimeoutInterval() time.Duration {
|
||||
return 5 * time.Second
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
@@ -60,6 +60,16 @@ func getKey(metadata *C.Metadata) 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 {
|
||||
var b, j int64
|
||||
|
||||
@@ -140,56 +150,31 @@ func strategyConsistentHashing() strategyFn {
|
||||
}
|
||||
|
||||
func strategyStickySessions() strategyFn {
|
||||
timeout := int64(600)
|
||||
type Session struct {
|
||||
idx int
|
||||
time time.Time
|
||||
}
|
||||
Sessions := make(map[string]map[string]Session)
|
||||
go func() {
|
||||
for true {
|
||||
time.Sleep(time.Second * 60)
|
||||
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
|
||||
ttl := time.Minute * 10
|
||||
|
||||
c := cache.New[uint64, int](1 * time.Second)
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
||||
length := len(proxies)
|
||||
idx, expireTime := c.GetWithExpire(key)
|
||||
if expireTime.IsZero() {
|
||||
idx = int(jumpHash(key+uint64(time.Now().UnixMilli()), int32(length)))
|
||||
}
|
||||
|
||||
var i int
|
||||
var res C.Proxy
|
||||
for i := 0; i < length; i++ {
|
||||
idx := (session.idx + i) % length
|
||||
proxy := proxies[idx]
|
||||
nowIdx := (idx + 1) % length
|
||||
proxy := proxies[nowIdx]
|
||||
if proxy.Alive() {
|
||||
session.idx = idx
|
||||
res = proxy
|
||||
break
|
||||
if nowIdx != idx {
|
||||
c.Put(key, idx, -1)
|
||||
c.Put(key, nowIdx, ttl)
|
||||
}
|
||||
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
if i == length {
|
||||
session.idx = 0
|
||||
res = proxies[0]
|
||||
}
|
||||
Sessions[src][dest] = session
|
||||
return res
|
||||
|
||||
return proxies[0]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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...)...)
|
||||
if err == nil {
|
||||
c.AppendToChains(u)
|
||||
u.failedTimes.Store(-1)
|
||||
u.failedTime.Store(-1)
|
||||
u.onDialSuccess()
|
||||
} else {
|
||||
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...)...)
|
||||
if err == nil {
|
||||
pc.AppendToChains(u)
|
||||
u.onDialSuccess()
|
||||
} else {
|
||||
u.onDialFailed()
|
||||
}
|
||||
|
||||
return pc, err
|
||||
|
||||
@@ -33,9 +33,10 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
|
||||
|
||||
type DomainMatcher struct {
|
||||
matchers strmatcher.IndexMatcher
|
||||
not bool
|
||||
}
|
||||
|
||||
func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
|
||||
func NewMphMatcherGroup(domains []*Domain, not bool) (*DomainMatcher, error) {
|
||||
g := strmatcher.NewMphMatcherGroup()
|
||||
for _, d := range domains {
|
||||
matcherType, f := matcherTypeMap[d.Type]
|
||||
@@ -50,11 +51,12 @@ func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
|
||||
g.Build()
|
||||
return &DomainMatcher{
|
||||
matchers: g,
|
||||
not: not,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewDomainMatcher new domain matcher.
|
||||
func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
|
||||
func NewDomainMatcher(domains []*Domain, not bool) (*DomainMatcher, error) {
|
||||
g := new(strmatcher.MatcherGroup)
|
||||
for _, d := range domains {
|
||||
m, err := domainToMatcher(d)
|
||||
@@ -66,11 +68,16 @@ func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
|
||||
|
||||
return &DomainMatcher{
|
||||
matchers: g,
|
||||
not: not,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var geoLoaderName = "memconservative"
|
||||
@@ -35,6 +35,16 @@ func Verify(name string) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@@ -50,7 +60,7 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
|
||||
matcher, err := router.NewDomainMatcher(domains)
|
||||
mph:minimal perfect hash algorithm
|
||||
*/
|
||||
matcher, err := router.NewMphMatcherGroup(domains)
|
||||
matcher, err := router.NewMphMatcherGroup(domains, not)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -59,12 +69,21 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, 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)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -72,7 +91,7 @@ func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
|
||||
geoIP := &router.GeoIP{
|
||||
CountryCode: country,
|
||||
Cidr: records,
|
||||
ReverseMatch: strings.Contains(country, "!"),
|
||||
ReverseMatch: not,
|
||||
}
|
||||
|
||||
matcher, err := router.NewGeoIPMatcher(geoIP)
|
||||
|
||||
@@ -117,13 +117,13 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta
|
||||
|
||||
host, err := sniffer.SniffTCP(bytes)
|
||||
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
|
||||
}
|
||||
|
||||
_, err = netip.ParseAddr(host)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -99,12 +99,6 @@ type FallbackFilter struct {
|
||||
GeoSite []*router.DomainMatcher `yaml:"geosite"`
|
||||
}
|
||||
|
||||
var (
|
||||
GroupsList = list.New()
|
||||
ProxiesList = list.New()
|
||||
ParsingProxiesCallback func(groupsList *list.List, proxiesList *list.List)
|
||||
)
|
||||
|
||||
// Profile config
|
||||
type Profile struct {
|
||||
StoreSelected bool `yaml:"store-selected"`
|
||||
@@ -130,7 +124,6 @@ type IPTables struct {
|
||||
|
||||
type Sniffer struct {
|
||||
Enable bool
|
||||
Force bool
|
||||
Sniffers []sniffer.Type
|
||||
Reverses *trie.DomainTrie[bool]
|
||||
ForceDomain *trie.DomainTrie[bool]
|
||||
@@ -213,7 +206,7 @@ type RawConfig struct {
|
||||
GeodataLoader string `yaml:"geodata-loader"`
|
||||
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"`
|
||||
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||
Hosts map[string]string `yaml:"hosts"`
|
||||
@@ -227,14 +220,11 @@ type RawConfig struct {
|
||||
Rule []string `yaml:"rules"`
|
||||
}
|
||||
|
||||
type SnifferRaw struct {
|
||||
type RawSniffer struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
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"`
|
||||
SkipDomain []string `yaml:"skip-domain" json:"skip-domain"`
|
||||
SkipSNI []string `yaml:"skip-sni" json:"skip-sni"`
|
||||
Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
|
||||
}
|
||||
|
||||
@@ -304,11 +294,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
"www.msftconnecttest.com",
|
||||
},
|
||||
},
|
||||
Sniffer: SnifferRaw{
|
||||
Sniffer: RawSniffer{
|
||||
Enable: false,
|
||||
Force: false,
|
||||
Sniffing: []string{},
|
||||
Reverse: []string{},
|
||||
ForceDomain: []string{},
|
||||
SkipDomain: []string{},
|
||||
Ports: []string{},
|
||||
@@ -432,8 +420,8 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
providersConfig := cfg.ProxyProvider
|
||||
|
||||
var proxyList []string
|
||||
_proxiesList := list.New()
|
||||
_groupsList := list.New()
|
||||
proxiesList := list.New()
|
||||
groupsList := list.New()
|
||||
|
||||
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
||||
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
|
||||
proxyList = append(proxyList, proxy.Name())
|
||||
_proxiesList.PushBack(mapping)
|
||||
proxiesList.PushBack(mapping)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
proxyList = append(proxyList, groupName)
|
||||
_groupsList.PushBack(mapping)
|
||||
groupsList.PushBack(mapping)
|
||||
}
|
||||
|
||||
// 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},
|
||||
)
|
||||
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||
ProxiesList = _proxiesList
|
||||
GroupsList = _groupsList
|
||||
if ParsingProxiesCallback != nil {
|
||||
// refresh tray menu
|
||||
go ParsingProxiesCallback(GroupsList, ProxiesList)
|
||||
}
|
||||
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
@@ -919,10 +902,9 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) {
|
||||
func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
sniffer := &Sniffer{
|
||||
Enable: snifferRaw.Enable,
|
||||
Force: snifferRaw.Force,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
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]()
|
||||
for _, domain := range snifferRaw.SkipDomain {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -89,9 +89,6 @@ type Metadata struct {
|
||||
RemoteDst string `json:"remoteDestination"`
|
||||
}
|
||||
|
||||
// avoid stack overflow
|
||||
type jsonMetadata Metadata
|
||||
|
||||
func (m *Metadata) RemoteAddress() string {
|
||||
return net.JoinHostPort(m.String(), m.DstPort)
|
||||
}
|
||||
|
||||
@@ -46,3 +46,11 @@ func (re *RuleExtra) NotMatchProcessName(processName string) bool {
|
||||
type RuleGeoSite interface {
|
||||
GetDomainMatcher() *router.DomainMatcher
|
||||
}
|
||||
|
||||
type RuleGeoIP interface {
|
||||
GetIPMatcher() *router.GeoIPMatcher
|
||||
}
|
||||
|
||||
type RuleGroup interface {
|
||||
GetRecodeSize() int
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ func (dc *quicClient) openSession() (quic.Connection, error) {
|
||||
return nil, fmt.Errorf("quio create packet failed")
|
||||
}
|
||||
|
||||
udp = wrapConn.PacketConn
|
||||
udp = wrapConn
|
||||
}
|
||||
|
||||
session, err := quic.Dial(udp, &udpAddr, host, tlsConfig, quicConfig)
|
||||
|
||||
@@ -164,6 +164,7 @@ func withResolver(resolver *Resolver) handler {
|
||||
msg.SetRcode(r, msg.Rcode)
|
||||
msg.Authoritative = true
|
||||
|
||||
log.Debugln("[DNS] %s --> %s", msgToDomain(r), msgToIP(msg))
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"go.uber.org/atomic"
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
@@ -232,7 +231,7 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
||||
return nil
|
||||
}
|
||||
|
||||
domain := r.msgToDomain(m)
|
||||
domain := msgToDomain(m)
|
||||
if domain == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -251,7 +250,7 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
domain := r.msgToDomain(m)
|
||||
domain := msgToDomain(m)
|
||||
|
||||
if domain == "" {
|
||||
return false
|
||||
@@ -332,14 +331,6 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ips []netip.Addr, err
|
||||
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 {
|
||||
ch := make(chan *result, 1)
|
||||
go func() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
@@ -116,6 +117,14 @@ func msgToIP(msg *D.Msg) []netip.Addr {
|
||||
return ips
|
||||
}
|
||||
|
||||
func msgToDomain(msg *D.Msg) string {
|
||||
if len(msg.Question) > 0 {
|
||||
return strings.TrimRight(msg.Question[0].Name, ".")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
type wrapPacketConn struct {
|
||||
net.PacketConn
|
||||
rAddr net.Addr
|
||||
|
||||
@@ -79,7 +79,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateSniffer(cfg.Sniffer)
|
||||
updateHosts(cfg.Hosts)
|
||||
initInnerTcp()
|
||||
updateDNS(cfg.DNS)
|
||||
updateDNS(cfg.DNS, cfg.General.IPv6)
|
||||
loadProxyProvider(cfg.Providers)
|
||||
updateProfile(cfg)
|
||||
loadRuleProvider(cfg.RuleProviders)
|
||||
@@ -125,13 +125,16 @@ func GetGeneral() *config.General {
|
||||
|
||||
func updateExperimental(c *config.Config) {}
|
||||
|
||||
func updateDNS(c *config.DNS) {
|
||||
func updateDNS(c *config.DNS, generalIPv6 bool) {
|
||||
if !c.Enable {
|
||||
resolver.DisableIPv6 = !generalIPv6
|
||||
resolver.DefaultResolver = nil
|
||||
resolver.DefaultHostMapper = nil
|
||||
resolver.DefaultLocalServer = nil
|
||||
dns.ReCreateServer("", nil, nil)
|
||||
return
|
||||
} else {
|
||||
resolver.DisableIPv6 = !c.IPv6
|
||||
}
|
||||
|
||||
cfg := dns.Config{
|
||||
@@ -153,8 +156,6 @@ func updateDNS(c *config.DNS) {
|
||||
ProxyServer: c.ProxyServerNameserver,
|
||||
}
|
||||
|
||||
resolver.DisableIPv6 = !cfg.IPv6
|
||||
|
||||
r := dns.NewResolver(cfg)
|
||||
pr := dns.NewProxyServerHostResolver(r)
|
||||
m := dns.NewEnhancer(cfg)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
@@ -19,17 +20,23 @@ type Rule struct {
|
||||
Type string `json:"type"`
|
||||
Payload string `json:"payload"`
|
||||
Proxy string `json:"proxy"`
|
||||
Size int `json:"Size"`
|
||||
}
|
||||
|
||||
func getRules(w http.ResponseWriter, r *http.Request) {
|
||||
rawRules := tunnel.Rules()
|
||||
rules := []Rule{}
|
||||
for _, rule := range rawRules {
|
||||
rules = append(rules, Rule{
|
||||
r := Rule{
|
||||
Type: rule.RuleType().String(),
|
||||
Payload: rule.Payload(),
|
||||
Proxy: rule.Adapter(),
|
||||
})
|
||||
Size: -1,
|
||||
}
|
||||
if rule.RuleType() == constant.GEOIP || rule.RuleType() == constant.GEOSITE {
|
||||
r.Size = rule.(constant.RuleGroup).GetRecodeSize()
|
||||
}
|
||||
rules = append(rules, r)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ func DefaultInterfaceChangeMonitor() {
|
||||
interfaceName, err := GetAutoDetectInterface()
|
||||
if err != nil {
|
||||
log.Warnln("[TUN] default interface monitor exited, cause: %v", err)
|
||||
break
|
||||
continue
|
||||
}
|
||||
|
||||
old := dialer.DefaultInterface.Load()
|
||||
|
||||
@@ -10,18 +10,18 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetAutoDetectInterface() (string, error) {
|
||||
res, err := cmd.ExecCmd("sh -c ip route | awk '{print $3}' | xargs echo -n")
|
||||
if err != nil {
|
||||
return "", err
|
||||
func GetAutoDetectInterface() (ifn string, err error) {
|
||||
cmdRes, err := cmd.ExecCmd("ip route get 1.1.1.1 uid 4294967295")
|
||||
|
||||
sps := strings.Split(cmdRes, " ")
|
||||
if len(sps) > 4 {
|
||||
ifn = sps[4]
|
||||
}
|
||||
ifaces := strings.Split(res, " ")
|
||||
for _, iface := range ifaces {
|
||||
if iface == "wlan0" {
|
||||
return "wlan0", nil
|
||||
}
|
||||
|
||||
if ifn == "" {
|
||||
err = fmt.Errorf("interface not found")
|
||||
}
|
||||
return ifaces[0], nil
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if err = execRouterCmd("add", addr.Masked().String(), interfaceName, ip.String(), "main"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if autoRoute {
|
||||
err = configInterfaceRouting(interfaceName, addr, autoDetectInterface)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
D "github.com/Dreamacro/clash/listener/tun/ipstack/commons"
|
||||
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/adapter"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
@@ -39,8 +38,6 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
|
||||
|
||||
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
|
||||
go func() {
|
||||
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
|
||||
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
defer func() {
|
||||
_ = pool.Put(buf)
|
||||
@@ -123,8 +120,6 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
|
||||
}
|
||||
|
||||
_, _ = tunConn.WriteTo(msg, addr)
|
||||
|
||||
log.Debugln("[TUN] hijack dns udp: %s", rAddr.String())
|
||||
}()
|
||||
|
||||
continue
|
||||
|
||||
@@ -93,8 +93,6 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
|
||||
|
||||
if D.ShouldHijackDns(dnsAddr, rAddrPort) {
|
||||
go func() {
|
||||
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
|
||||
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
defer func() {
|
||||
_ = pool.Put(buf)
|
||||
@@ -186,8 +184,6 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
|
||||
_, _ = stack.UDP().WriteTo(msg, rAddr, lAddr)
|
||||
|
||||
_ = pool.Put(buf)
|
||||
|
||||
log.Debugln("[TUN] hijack dns udp: %s", rAddrPort.String())
|
||||
}()
|
||||
|
||||
continue
|
||||
|
||||
@@ -18,6 +18,7 @@ type GEOIP struct {
|
||||
adapter string
|
||||
noResolveIP bool
|
||||
geoIPMatcher *router.GeoIPMatcher
|
||||
recodeSize int
|
||||
}
|
||||
|
||||
func (g *GEOIP) RuleType() C.RuleType {
|
||||
@@ -65,6 +66,10 @@ func (g *GEOIP) GetIPMatcher() *router.GeoIPMatcher {
|
||||
return g.geoIPMatcher
|
||||
}
|
||||
|
||||
func (g *GEOIP) GetRecodeSize() int {
|
||||
return g.recodeSize
|
||||
}
|
||||
|
||||
func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) {
|
||||
if !C.GeodataMode {
|
||||
geoip := &GEOIP{
|
||||
@@ -76,18 +81,19 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error)
|
||||
return geoip, nil
|
||||
}
|
||||
|
||||
geoIPMatcher, recordsCount, err := geodata.LoadGeoIPMatcher(country)
|
||||
geoIPMatcher, size, err := geodata.LoadGeoIPMatcher(country)
|
||||
if err != nil {
|
||||
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{
|
||||
Base: &Base{},
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
noResolveIP: noResolveIP,
|
||||
geoIPMatcher: geoIPMatcher,
|
||||
recodeSize: size,
|
||||
}
|
||||
return geoip, nil
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ import (
|
||||
|
||||
type GEOSITE struct {
|
||||
*Base
|
||||
country string
|
||||
adapter string
|
||||
matcher *router.DomainMatcher
|
||||
country string
|
||||
adapter string
|
||||
matcher *router.DomainMatcher
|
||||
recodeSize int
|
||||
}
|
||||
|
||||
func (gs *GEOSITE) RuleType() C.RuleType {
|
||||
@@ -44,19 +45,24 @@ func (gs *GEOSITE) GetDomainMatcher() *router.DomainMatcher {
|
||||
return gs.matcher
|
||||
}
|
||||
|
||||
func (gs *GEOSITE) GetRecodeSize() int {
|
||||
return gs.recodeSize
|
||||
}
|
||||
|
||||
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
||||
matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country)
|
||||
matcher, size, err := geodata.LoadGeoSiteMatcher(country)
|
||||
if err != nil {
|
||||
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{
|
||||
Base: &Base{},
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
matcher: matcher,
|
||||
Base: &Base{},
|
||||
country: country,
|
||||
adapter: adapter,
|
||||
matcher: matcher,
|
||||
recodeSize: size,
|
||||
}
|
||||
|
||||
return geoSite, nil
|
||||
|
||||
@@ -102,7 +102,8 @@ func parseRule(tp, payload string, params []string) (C.Rule, error) {
|
||||
case "PROCESS-PATH":
|
||||
parsed, parseErr = RC.NewProcess(payload, "", false)
|
||||
case "RULE-SET":
|
||||
parsed, parseErr = provider.NewRuleSet(payload, "")
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = provider.NewRuleSet(payload, "", noResolve)
|
||||
case "NOT":
|
||||
parsed, parseErr = NewNOT(payload, "")
|
||||
case "AND":
|
||||
|
||||
@@ -50,7 +50,8 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) {
|
||||
case "NOT":
|
||||
parsed, parseErr = logic.NewNOT(payload, target)
|
||||
case "RULE-SET":
|
||||
parsed, parseErr = RP.NewRuleSet(payload, target)
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RP.NewRuleSet(payload, target, noResolve)
|
||||
case "MATCH":
|
||||
parsed = RC.NewMatch(target)
|
||||
default:
|
||||
|
||||
@@ -30,26 +30,20 @@ func (c *classicalStrategy) ShouldResolveIP() bool {
|
||||
}
|
||||
|
||||
func (c *classicalStrategy) OnUpdate(rules []string) {
|
||||
var classicalRules []C.Rule
|
||||
shouldResolveIP := false
|
||||
count := 0
|
||||
for _, rawRule := range rules {
|
||||
ruleType, rule, params := ruleParse(rawRule)
|
||||
r, err := parseRule(ruleType, rule, "", params)
|
||||
if err != nil {
|
||||
log.Warnln("parse rule error:[%s]", err.Error())
|
||||
} else {
|
||||
if !shouldResolveIP {
|
||||
shouldResolveIP = shouldResolveIP || r.ShouldResolveIP()
|
||||
if !c.shouldResolveIP {
|
||||
c.shouldResolveIP = r.ShouldResolveIP()
|
||||
}
|
||||
|
||||
classicalRules = append(classicalRules, r)
|
||||
count++
|
||||
c.rules = append(c.rules, r)
|
||||
c.count++
|
||||
}
|
||||
}
|
||||
|
||||
c.rules = classicalRules
|
||||
c.count = count
|
||||
}
|
||||
|
||||
func NewClassicalStrategy() *classicalStrategy {
|
||||
|
||||
@@ -8,9 +8,8 @@ import (
|
||||
)
|
||||
|
||||
type domainStrategy struct {
|
||||
shouldResolveIP bool
|
||||
count int
|
||||
domainRules *trie.DomainTrie[bool]
|
||||
count int
|
||||
domainRules *trie.DomainTrie[bool]
|
||||
}
|
||||
|
||||
func (d *domainStrategy) Match(metadata *C.Metadata) bool {
|
||||
@@ -22,7 +21,7 @@ func (d *domainStrategy) Count() int {
|
||||
}
|
||||
|
||||
func (d *domainStrategy) ShouldResolveIP() bool {
|
||||
return d.shouldResolveIP
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *domainStrategy) OnUpdate(rules []string) {
|
||||
@@ -55,5 +54,5 @@ func ruleParse(ruleRaw string) (string, string, []string) {
|
||||
}
|
||||
|
||||
func NewDomainStrategy() *domainStrategy {
|
||||
return &domainStrategy{shouldResolveIP: false}
|
||||
return &domainStrategy{}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type RuleSet struct {
|
||||
ruleProviderName string
|
||||
adapter string
|
||||
ruleProvider P.RuleProvider
|
||||
noResolveIP bool
|
||||
}
|
||||
|
||||
func (rs *RuleSet) ShouldFindProcess() bool {
|
||||
@@ -35,7 +36,7 @@ func (rs *RuleSet) Payload() string {
|
||||
}
|
||||
|
||||
func (rs *RuleSet) ShouldResolveIP() bool {
|
||||
return rs.getProviders().ShouldResolveIP()
|
||||
return !rs.noResolveIP && rs.getProviders().ShouldResolveIP()
|
||||
}
|
||||
func (rs *RuleSet) getProviders() P.RuleProvider {
|
||||
if rs.ruleProvider == nil {
|
||||
@@ -46,7 +47,7 @@ func (rs *RuleSet) getProviders() P.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]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("rule set %s not found", ruleProviderName)
|
||||
@@ -56,5 +57,6 @@ func NewRuleSet(ruleProviderName string, adapter string) (*RuleSet, error) {
|
||||
ruleProviderName: ruleProviderName,
|
||||
adapter: adapter,
|
||||
ruleProvider: rp,
|
||||
noResolveIP: noResolveIP,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user