Meow
2026-05-07 18:59:23 +08:00
committed by RPRX
parent 4192ca0827
commit 906d49a271
4 changed files with 104 additions and 85 deletions

View File

@@ -5,18 +5,16 @@ import (
"context" "context"
go_errors "errors" go_errors "errors"
"fmt" "fmt"
"os"
"runtime"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/dns" "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 { if s.checkSystem {
supportIPv4, supportIPv6 := checkRoutes() supportIPv4, supportIPv6 := utils.CheckRoutes()
option.IPv4Enable = option.IPv4Enable && supportIPv4 option.IPv4Enable = option.IPv4Enable && supportIPv4
option.IPv6Enable = option.IPv6Enable && supportIPv6 option.IPv6Enable = option.IPv6Enable && supportIPv6
} else { } else {
@@ -539,67 +537,3 @@ func init() {
return New(ctx, config.(*Config)) 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
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "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/core"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/routing" "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. // 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) { func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
if c.checkSystem { if c.checkSystem {
supportIPv4, supportIPv6 := checkRoutes() supportIPv4, supportIPv6 := utils.CheckRoutes()
option.IPv4Enable = option.IPv4Enable && supportIPv4 option.IPv4Enable = option.IPv4Enable && supportIPv4
option.IPv6Enable = option.IPv6Enable && supportIPv6 option.IPv6Enable = option.IPv6Enable && supportIPv6
} else { } else {

View File

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

View File

@@ -239,11 +239,11 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
min = rule.blockDelay.Min min = rule.blockDelay.Min
max = rule.blockDelay.Max max = rule.blockDelay.Max
} }
abs := max - min span := max - min
if 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 { 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 conn stat.Connection
var blockedDest *net.Destination var blockedDest *net.Destination
var blockedRule *FinalRule var blockedRule *FinalRule
firstResolve := true
err := retry.ExponentialBackoff(5, 100).On(func() error { err := retry.ExponentialBackoff(5, 100).On(func() error {
dialDest := destination dialDest := destination
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() { 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) ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
if err != nil { if err != nil {
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain()) 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 return err
} }
} else { } else {
@@ -315,14 +316,29 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
errors.LogInfo(ctx, "dialing to ", dialDest) errors.LogInfo(ctx, "dialing to ", dialDest)
} }
} else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules } else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, dialDest.Address.Domain()) domain := dialDest.Address.Domain()
if err != nil { var ips []net.IP
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain()) if firstResolve {
} else if len(addrs) > 0 { firstResolve = false
if addr := net.IPAddress(addrs[dice.Roll(len(addrs))].IP); addr != nil { supportIPv4, supportIPv6 := utils.CheckRoutes()
dialDest.Address = addr if supportIPv4 {
errors.LogInfo(ctx, "dialing to ", dialDest) 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 { 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 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 { if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
version := byte(h.config.ProxyProtocol) version := byte(h.config.ProxyProtocol)
srcAddr := inbound.Source.RawNetAddr() srcAddr := inbound.Source.RawNetAddr()