From cb0dda1b9fd68afebc708815ae792ca6e92dcdf8 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Tue, 21 Oct 2025 01:12:07 +0800 Subject: [PATCH 1/4] DNS: cache network capability check on non-mobile platforms --- app/dns/dns.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/dns/dns.go b/app/dns/dns.go index 37183abaa8f3..23836f8a2288 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -5,6 +5,7 @@ import ( "context" go_errors "errors" "fmt" + "runtime" "sort" "strings" "sync" @@ -328,7 +329,7 @@ func init() { })) } -func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) { +func checkSystemNetworkInternal() (supportIPv4 bool, supportIPv6 bool) { conn4, err4 := net.Dial("udp4", "192.33.4.12:53") if err4 != nil { supportIPv4 = false @@ -346,3 +347,20 @@ func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) { } return } + +var cachedSystemNetwork struct { + sync.Once + ipv4 bool + ipv6 bool +} + +func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) { + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + return checkSystemNetworkInternal() + } + + cachedSystemNetwork.Once.Do(func() { + cachedSystemNetwork.ipv4, cachedSystemNetwork.ipv6 = checkSystemNetworkInternal() + }) + return cachedSystemNetwork.ipv4, cachedSystemNetwork.ipv6 +} From 9e43e3f9d121e7137e741c2dd9cbb8879352080d Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Tue, 21 Oct 2025 05:39:43 +0800 Subject: [PATCH 2/4] cover edge case --- app/dns/dns.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/dns/dns.go b/app/dns/dns.go index 23836f8a2288..2b96c49b051c 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -5,6 +5,7 @@ import ( "context" go_errors "errors" "fmt" + "os" "runtime" "sort" "strings" @@ -355,7 +356,7 @@ var cachedSystemNetwork struct { } func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) { - if runtime.GOOS == "android" || runtime.GOOS == "ios" { + if isGUIPlatform() { return checkSystemNetworkInternal() } @@ -364,3 +365,18 @@ func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) { }) return cachedSystemNetwork.ipv4, cachedSystemNetwork.ipv6 } + +func isGUIPlatform() bool { + switch runtime.GOOS { + case "android", "ios", "windows", "darwin": + return true + case "linux", "freebsd", "openbsd": + if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" { + return true + } + if os.Getenv("DISPLAY") != "" { + return true + } + } + return false +} From fe94d4ac0fd932fbfa7f04c6b5234e5d2ddab622 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Sun, 26 Oct 2025 21:19:09 +0800 Subject: [PATCH 3/4] refactor --- app/dns/dns.go | 64 +++++++++++++++++++++++++------------------ app/dns/nameserver.go | 2 +- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/app/dns/dns.go b/app/dns/dns.go index 2b96c49b051c..ab8252954a5e 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -10,6 +10,7 @@ import ( "sort" "strings" "sync" + "time" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/errors" @@ -193,7 +194,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er } if s.checkSystem { - supportIPv4, supportIPv6 := checkSystemNetwork() + supportIPv4, supportIPv6 := checkRoutes() option.IPv4Enable = option.IPv4Enable && supportIPv4 option.IPv6Enable = option.IPv6Enable && supportIPv6 } else { @@ -330,40 +331,51 @@ func init() { })) } -func checkSystemNetworkInternal() (supportIPv4 bool, supportIPv6 bool) { - conn4, err4 := net.Dial("udp4", "192.33.4.12:53") - if err4 != nil { - supportIPv4 = false - } else { - supportIPv4 = true - conn4.Close() +func probeRoutes() (ipv4 bool, ipv6 bool) { + if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil { + ipv4 = true + conn.Close() } - - conn6, err6 := net.Dial("udp6", "[2001:500:2::c]:53") - if err6 != nil { - supportIPv6 = false - } else { - supportIPv6 = true - conn6.Close() + if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil { + ipv6 = true + conn.Close() } return } -var cachedSystemNetwork struct { +var routeCache struct { sync.Once - ipv4 bool - ipv6 bool + sync.RWMutex + expire time.Time + ipv4, ipv6 bool } -func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) { - if isGUIPlatform() { - return checkSystemNetworkInternal() +func checkRoutes() (bool, bool) { + if !isGUIPlatform() { + routeCache.Once.Do(func() { + routeCache.ipv4, routeCache.ipv6 = probeRoutes() + }) + return routeCache.ipv4, routeCache.ipv6 } - cachedSystemNetwork.Once.Do(func() { - cachedSystemNetwork.ipv4, cachedSystemNetwork.ipv6 = checkSystemNetworkInternal() - }) - return cachedSystemNetwork.ipv4, cachedSystemNetwork.ipv6 + routeCache.RWMutex.RLock() + now := time.Now() + if routeCache.expire.After(now) { + routeCache.RWMutex.RUnlock() + return routeCache.ipv4, routeCache.ipv6 + } + routeCache.RWMutex.RUnlock() + + routeCache.RWMutex.Lock() + defer routeCache.RWMutex.Unlock() + + now = time.Now() + if routeCache.expire.After(now) { // double-check + return routeCache.ipv4, routeCache.ipv6 + } + routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms + routeCache.expire = now.Add(100 * time.Millisecond) // ttl + return routeCache.ipv4, routeCache.ipv6 } func isGUIPlatform() bool { @@ -374,7 +386,7 @@ func isGUIPlatform() bool { if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" { return true } - if os.Getenv("DISPLAY") != "" { + if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" { return true } } diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go index cf1b665bd080..d8d451b74eaf 100644 --- a/app/dns/nameserver.go +++ b/app/dns/nameserver.go @@ -218,7 +218,7 @@ func (c *Client) IsFinalQuery() bool { // QueryIP sends DNS query to the name server with the client's IP. func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) { if c.checkSystem { - supportIPv4, supportIPv6 := checkSystemNetwork() + supportIPv4, supportIPv6 := checkRoutes() option.IPv4Enable = option.IPv4Enable && supportIPv4 option.IPv6Enable = option.IPv6Enable && supportIPv6 } else { From f2339d20fbc9b31e7fb83fc3b16e35ec48558d1b Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:40:44 +0800 Subject: [PATCH 4/4] cache isGUIPlatform --- app/dns/dns.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/dns/dns.go b/app/dns/dns.go index ab8252954a5e..a59fc3985664 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -351,7 +351,7 @@ var routeCache struct { } func checkRoutes() (bool, bool) { - if !isGUIPlatform() { + if !isGUIPlatform { routeCache.Once.Do(func() { routeCache.ipv4, routeCache.ipv6 = probeRoutes() }) @@ -378,7 +378,9 @@ func checkRoutes() (bool, bool) { return routeCache.ipv4, routeCache.ipv6 } -func isGUIPlatform() bool { +var isGUIPlatform = detectGUIPlatform() + +func detectGUIPlatform() bool { switch runtime.GOOS { case "android", "ios", "windows", "darwin": return true