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"
"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(),
})
}

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...)...)
if err == nil {
pc.AppendToChains(f)
f.onDialSuccess()
} else {
f.onDialFailed()
}
return pc, err

View File

@@ -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
}

View File

@@ -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]
}
}

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...)...)
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

View File

@@ -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.

View File

@@ -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)
mphminimal 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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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":

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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{}
}

View File

@@ -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
}