diff --git a/app/dns/dns.go b/app/dns/dns.go index 8b52a6a5..abf6894b 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -5,18 +5,16 @@ 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" "github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/features/dns" ) @@ -223,7 +221,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er } if s.checkSystem { - supportIPv4, supportIPv6 := checkRoutes() + supportIPv4, supportIPv6 := utils.CheckRoutes() option.IPv4Enable = option.IPv4Enable && supportIPv4 option.IPv6Enable = option.IPv6Enable && supportIPv6 } else { @@ -539,67 +537,3 @@ func init() { return New(ctx, config.(*Config)) })) } - -func probeRoutes() (ipv4 bool, ipv6 bool) { - if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil { - ipv4 = true - conn.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 -} diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go index 8b003eaa..91862453 100644 --- a/app/dns/nameserver.go +++ b/app/dns/nameserver.go @@ -10,6 +10,7 @@ import ( "github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/routing" @@ -166,7 +167,7 @@ func (c *Client) Name() string { // 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 := checkRoutes() + supportIPv4, supportIPv6 := utils.CheckRoutes() option.IPv4Enable = option.IPv4Enable && supportIPv4 option.IPv6Enable = option.IPv6Enable && supportIPv6 } else { diff --git a/common/utils/probe_routes.go b/common/utils/probe_routes.go new file mode 100644 index 00000000..6e804b41 --- /dev/null +++ b/common/utils/probe_routes.go @@ -0,0 +1,73 @@ +package utils + +import ( + "net" + "os" + "runtime" + "sync" + "time" +) + +func probeRoutes() (ipv4 bool, ipv6 bool) { + if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil { + ipv4 = true + conn.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 +} diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 4cd48b80..f8181a34 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -239,11 +239,11 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration { min = rule.blockDelay.Min max = rule.blockDelay.Max } - abs := max - min + span := max - min if max < min { - abs = min - max + span = min - max } - return time.Duration(min+uint64(dice.Roll(int(abs+1)))) * time.Second + return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second } func isValidAddress(addr *net.IPOrDomain) bool { @@ -293,6 +293,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte var conn stat.Connection var blockedDest *net.Destination var blockedRule *FinalRule + firstResolve := true err := retry.ExponentialBackoff(5, 100).On(func() error { dialDest := destination if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() { @@ -303,7 +304,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway) if err != nil { errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain()) - if h.config.DomainStrategy.ForceIP() { + if h.config.DomainStrategy.ForceIP() || h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { return err } } else { @@ -315,14 +316,29 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte errors.LogInfo(ctx, "dialing to ", dialDest) } } else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules - addrs, err := net.DefaultResolver.LookupIPAddr(ctx, dialDest.Address.Domain()) - if err != nil { - errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain()) - } else if len(addrs) > 0 { - if addr := net.IPAddress(addrs[dice.Roll(len(addrs))].IP); addr != nil { - dialDest.Address = addr - errors.LogInfo(ctx, "dialing to ", dialDest) + domain := dialDest.Address.Domain() + var ips []net.IP + if firstResolve { + firstResolve = false + supportIPv4, supportIPv6 := utils.CheckRoutes() + if supportIPv4 { + ips, _ = net.DefaultResolver.LookupIP(ctx, "ip4", domain) } + if len(ips) == 0 && supportIPv6 { + ips, _ = net.DefaultResolver.LookupIP(ctx, "ip6", domain) + } + if len(ips) == 0 { + return errors.New("failed to get IP address for domain ", domain) + } + } else { + ips, _ = net.DefaultResolver.LookupIP(ctx, "ip", domain) + } + if len(ips) == 0 { // SRV/TXT, lookup failed + return errors.New("failed to get IP address for domain ", domain) + } + if addr := net.IPAddress(ips[dice.Roll(len(ips))]); addr != nil { + dialDest.Address = addr + errors.LogInfo(ctx, "dialing to ", dialDest) } } if rule := h.matchFinalRule(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block { @@ -357,11 +373,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } return nil } - // TODO: SRV/TXT - // if remoteDest := net.DestinationFromAddr(conn.RemoteAddr()); h.applyFinalRules(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule) == RuleAction_Block { - // conn.Close() - // return blackhole(remoteDest) - // } if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 { version := byte(h.config.ProxyProtocol) srcAddr := inbound.Source.RawNetAddr()