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

View File

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

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