Browse Source

perf(dns): cache network capability check (#5244)

Meow 2 weeks ago
parent
commit
4956e65824
2 changed files with 64 additions and 16 deletions
  1. 63 15
      app/dns/dns.go
  2. 1 1
      app/dns/nameserver.go

+ 63 - 15
app/dns/dns.go

@@ -5,9 +5,12 @@ import (
 	"context"
 	go_errors "errors"
 	"fmt"
+	"os"
+	"runtime"
 	"sort"
 	"strings"
 	"sync"
+	"time"
 
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/errors"
@@ -191,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 {
@@ -328,21 +331,66 @@ func init() {
 	}))
 }
 
-func checkSystemNetwork() (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 routeCache struct {
+	sync.Once
+	sync.RWMutex
+	expire     time.Time
+	ipv4, ipv6 bool
+}
+
+func checkRoutes() (bool, bool) {
+	if !isGUIPlatform {
+		routeCache.Once.Do(func() {
+			routeCache.ipv4, routeCache.ipv6 = probeRoutes()
+		})
+		return routeCache.ipv4, routeCache.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
+}
+
+var isGUIPlatform = detectGUIPlatform()
+
+func detectGUIPlatform() 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") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
+			return true
+		}
+	}
+	return false
+}

+ 1 - 1
app/dns/nameserver.go

@@ -216,7 +216,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 {