Skip to content

Commit d9fa5b4

Browse files
authored
Direct/Freedom outbound: Prefer IPv4 for finalRules' "AsIs" (#6075)
#6075 (comment) #6075 (comment) #6075 (comment)
1 parent 4192ca0 commit d9fa5b4

4 files changed

Lines changed: 104 additions & 85 deletions

File tree

app/dns/dns.go

Lines changed: 2 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,16 @@ import (
55
"context"
66
go_errors "errors"
77
"fmt"
8-
"os"
9-
"runtime"
108
"sort"
119
"strings"
1210
"sync"
13-
"time"
1411

1512
"github.com/xtls/xray-core/common"
1613
"github.com/xtls/xray-core/common/errors"
1714
"github.com/xtls/xray-core/common/geodata"
1815
"github.com/xtls/xray-core/common/net"
1916
"github.com/xtls/xray-core/common/session"
17+
"github.com/xtls/xray-core/common/utils"
2018
"github.com/xtls/xray-core/features/dns"
2119
)
2220

@@ -223,7 +221,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
223221
}
224222

225223
if s.checkSystem {
226-
supportIPv4, supportIPv6 := checkRoutes()
224+
supportIPv4, supportIPv6 := utils.CheckRoutes()
227225
option.IPv4Enable = option.IPv4Enable && supportIPv4
228226
option.IPv6Enable = option.IPv6Enable && supportIPv6
229227
} else {
@@ -539,67 +537,3 @@ func init() {
539537
return New(ctx, config.(*Config))
540538
}))
541539
}
542-
543-
func probeRoutes() (ipv4 bool, ipv6 bool) {
544-
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
545-
ipv4 = true
546-
conn.Close()
547-
}
548-
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
549-
ipv6 = true
550-
conn.Close()
551-
}
552-
return
553-
}
554-
555-
var routeCache struct {
556-
sync.Once
557-
sync.RWMutex
558-
expire time.Time
559-
ipv4, ipv6 bool
560-
}
561-
562-
func checkRoutes() (bool, bool) {
563-
if !isGUIPlatform {
564-
routeCache.Once.Do(func() {
565-
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
566-
})
567-
return routeCache.ipv4, routeCache.ipv6
568-
}
569-
570-
routeCache.RWMutex.RLock()
571-
now := time.Now()
572-
if routeCache.expire.After(now) {
573-
routeCache.RWMutex.RUnlock()
574-
return routeCache.ipv4, routeCache.ipv6
575-
}
576-
routeCache.RWMutex.RUnlock()
577-
578-
routeCache.RWMutex.Lock()
579-
defer routeCache.RWMutex.Unlock()
580-
581-
now = time.Now()
582-
if routeCache.expire.After(now) { // double-check
583-
return routeCache.ipv4, routeCache.ipv6
584-
}
585-
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
586-
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
587-
return routeCache.ipv4, routeCache.ipv6
588-
}
589-
590-
var isGUIPlatform = detectGUIPlatform()
591-
592-
func detectGUIPlatform() bool {
593-
switch runtime.GOOS {
594-
case "android", "ios", "windows", "darwin":
595-
return true
596-
case "linux", "freebsd", "openbsd":
597-
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
598-
return true
599-
}
600-
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
601-
return true
602-
}
603-
}
604-
return false
605-
}

app/dns/nameserver.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/xtls/xray-core/common/geodata"
1111
"github.com/xtls/xray-core/common/net"
1212
"github.com/xtls/xray-core/common/session"
13+
"github.com/xtls/xray-core/common/utils"
1314
"github.com/xtls/xray-core/core"
1415
"github.com/xtls/xray-core/features/dns"
1516
"github.com/xtls/xray-core/features/routing"
@@ -166,7 +167,7 @@ func (c *Client) Name() string {
166167
// QueryIP sends DNS query to the name server with the client's IP.
167168
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
168169
if c.checkSystem {
169-
supportIPv4, supportIPv6 := checkRoutes()
170+
supportIPv4, supportIPv6 := utils.CheckRoutes()
170171
option.IPv4Enable = option.IPv4Enable && supportIPv4
171172
option.IPv6Enable = option.IPv6Enable && supportIPv6
172173
} else {

common/utils/probe_routes.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package utils
2+
3+
import (
4+
"net"
5+
"os"
6+
"runtime"
7+
"sync"
8+
"time"
9+
)
10+
11+
func probeRoutes() (ipv4 bool, ipv6 bool) {
12+
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
13+
ipv4 = true
14+
conn.Close()
15+
}
16+
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
17+
ipv6 = true
18+
conn.Close()
19+
}
20+
return
21+
}
22+
23+
var routeCache struct {
24+
sync.Once
25+
sync.RWMutex
26+
expire time.Time
27+
ipv4, ipv6 bool
28+
}
29+
30+
func CheckRoutes() (bool, bool) {
31+
if !isGUIPlatform {
32+
routeCache.Once.Do(func() {
33+
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
34+
})
35+
return routeCache.ipv4, routeCache.ipv6
36+
}
37+
38+
routeCache.RWMutex.RLock()
39+
now := time.Now()
40+
if routeCache.expire.After(now) {
41+
routeCache.RWMutex.RUnlock()
42+
return routeCache.ipv4, routeCache.ipv6
43+
}
44+
routeCache.RWMutex.RUnlock()
45+
46+
routeCache.RWMutex.Lock()
47+
defer routeCache.RWMutex.Unlock()
48+
49+
now = time.Now()
50+
if routeCache.expire.After(now) { // double-check
51+
return routeCache.ipv4, routeCache.ipv6
52+
}
53+
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
54+
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
55+
return routeCache.ipv4, routeCache.ipv6
56+
}
57+
58+
var isGUIPlatform = detectGUIPlatform()
59+
60+
func detectGUIPlatform() bool {
61+
switch runtime.GOOS {
62+
case "android", "ios", "windows", "darwin":
63+
return true
64+
case "linux", "freebsd", "openbsd":
65+
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
66+
return true
67+
}
68+
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
69+
return true
70+
}
71+
}
72+
return false
73+
}

proxy/freedom/freedom.go

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,11 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
239239
min = rule.blockDelay.Min
240240
max = rule.blockDelay.Max
241241
}
242-
abs := max - min
242+
span := max - min
243243
if max < min {
244-
abs = min - max
244+
span = min - max
245245
}
246-
return time.Duration(min+uint64(dice.Roll(int(abs+1)))) * time.Second
246+
return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
247247
}
248248

249249
func isValidAddress(addr *net.IPOrDomain) bool {
@@ -293,6 +293,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
293293
var conn stat.Connection
294294
var blockedDest *net.Destination
295295
var blockedRule *FinalRule
296+
firstResolve := true
296297
err := retry.ExponentialBackoff(5, 100).On(func() error {
297298
dialDest := destination
298299
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
303304
ips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)
304305
if err != nil {
305306
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
306-
if h.config.DomainStrategy.ForceIP() {
307+
if h.config.DomainStrategy.ForceIP() || h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) {
307308
return err
308309
}
309310
} else {
@@ -315,14 +316,29 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
315316
errors.LogInfo(ctx, "dialing to ", dialDest)
316317
}
317318
} else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules
318-
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, dialDest.Address.Domain())
319-
if err != nil {
320-
errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())
321-
} else if len(addrs) > 0 {
322-
if addr := net.IPAddress(addrs[dice.Roll(len(addrs))].IP); addr != nil {
323-
dialDest.Address = addr
324-
errors.LogInfo(ctx, "dialing to ", dialDest)
319+
domain := dialDest.Address.Domain()
320+
var ips []net.IP
321+
if firstResolve {
322+
firstResolve = false
323+
supportIPv4, supportIPv6 := utils.CheckRoutes()
324+
if supportIPv4 {
325+
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip4", domain)
326+
}
327+
if len(ips) == 0 && supportIPv6 {
328+
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip6", domain)
329+
}
330+
if len(ips) == 0 {
331+
return errors.New("failed to get IP address for domain ", domain)
325332
}
333+
} else {
334+
ips, _ = net.DefaultResolver.LookupIP(ctx, "ip", domain)
335+
}
336+
if len(ips) == 0 { // SRV/TXT, lookup failed
337+
return errors.New("failed to get IP address for domain ", domain)
338+
}
339+
if addr := net.IPAddress(ips[dice.Roll(len(ips))]); addr != nil {
340+
dialDest.Address = addr
341+
errors.LogInfo(ctx, "dialing to ", dialDest)
326342
}
327343
}
328344
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
357373
}
358374
return nil
359375
}
360-
// TODO: SRV/TXT
361-
// if remoteDest := net.DestinationFromAddr(conn.RemoteAddr()); h.applyFinalRules(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule) == RuleAction_Block {
362-
// conn.Close()
363-
// return blackhole(remoteDest)
364-
// }
365376
if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {
366377
version := byte(h.config.ProxyProtocol)
367378
srcAddr := inbound.Source.RawNetAddr()

0 commit comments

Comments
 (0)