Skip to content

Commit aa287e8

Browse files
committed
feat(dns): implement IPv6 preference with latency testing and caching
- Enable IPv6 support by default (ipv6 = true) - Add IP preference system with multiple modes: * standard: prefer IPv6, fallback to IPv4 * fastest: test all IPs concurrently, pick lowest latency * ipv6: force IPv6 only * ipv4: force IPv4 only - Implement preference cache separate from DNS cache - Add resolver_preference.go with thread-safe cache - Replace resolveStrictIPv6 with resolveWithPreference - Introduce concurrent DNS queries (AAAA + A parallel) - Add latency testing with configurable timeout (500ms default) - Add cache TTL (216000 seconds = 60h default) - Add comprehensive unit tests and benchmarks - Update config.default.toml with new [preference] section - Update sample config.toml with bilingual documentation - Fix internal/config/defaults.go to use correct ruleslib symbols - Fix gendefaults tool compatibility - All tests passing, builds successfully
1 parent ae8942b commit aa287e8

10 files changed

Lines changed: 720 additions & 119 deletions

File tree

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
3333
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
3434
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
3535
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
36+
github.com/xihale/snirect-shared v1.1.1 h1:k6pKyYbABqNrYNy4EVO8amJZSfS2YI1svO993Q0ZZvM=
37+
github.com/xihale/snirect-shared v1.1.1/go.mod h1:eT47oR1HH5PX/omD/RO8KPQ3D8cGoOuMWHrAbuBVq+U=
3638
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
3739
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
3840
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=

internal/config/config.default.toml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
set_proxy = true
55
ca_install = "auto"
6-
ipv6 = false
6+
ipv6 = true
77
check_hostname = true
88
ecs = ""
99

@@ -31,3 +31,25 @@ logfile = ""
3131
address = "127.0.0.1"
3232
port = 7654
3333
pac_host = "127.0.0.1"
34+
35+
[preference]
36+
# Mode: standard (current), fastest (test both), ipv6 (force v6), ipv4 (force v4)
37+
mode = "standard"
38+
39+
# When mode="fastest", whether to actually test latency or just use first
40+
enable_testing = true
41+
42+
# Timeout for each connection test in milliseconds
43+
test_timeout_ms = 500
44+
45+
# Test IPs in parallel (true) or sequentially (false)
46+
test_parallel = true
47+
48+
# Maximum number of IPs to test per query (to avoid too many goroutines)
49+
max_test_ips = 10
50+
51+
# Preference cache TTL in seconds (0 = use DNS TTL / 2)
52+
cache_ttl = 216000
53+
54+
# Maximum number of entries in preference cache (0 = unlimited)
55+
cache_size = 5000

internal/config/config.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,36 @@ type Config struct {
8080

8181
// Server contains local proxy server settings.
8282
Server ServerConfig `toml:"server"`
83+
84+
// Preference contains DNS IP preference settings.
85+
Preference PreferenceConfig `toml:"preference"`
86+
}
87+
88+
// IPPreferenceMode defines how IP selection works when both IPv6 and IPv4 are available.
89+
type IPPreferenceMode string
90+
91+
const (
92+
IPPreferenceStandard IPPreferenceMode = "standard" // Current behavior: prefer v6 if enabled, then first
93+
IPPreferenceFastest IPPreferenceMode = "fastest" // Test both, use lowest latency (cached)
94+
IPPreferenceIPv6 IPPreferenceMode = "ipv6" // Always prefer IPv6 if available
95+
IPPreferenceIPv4 IPPreferenceMode = "ipv4" // Always prefer IPv4
96+
)
97+
98+
// PreferenceConfig contains DNS IP preference settings.
99+
type PreferenceConfig struct {
100+
Mode IPPreferenceMode `toml:"mode"` // Selection mode
101+
EnableTesting bool `toml:"enable_testing"` // Whether to do latency testing (for fastest mode)
102+
// TestTimeoutMs is the timeout for each connection test in milliseconds.
103+
// When testing IPs, we dial with this timeout and measure connection establishment time.
104+
TestTimeoutMs int `toml:"test_timeout_ms"`
105+
// TestParallel determines whether to test all IPs in parallel (true) or sequentially (false).
106+
TestParallel bool `toml:"test_parallel"`
107+
// MaxTestIPs limits the number of IPs to test per query to avoid resource exhaustion.
108+
MaxTestIPs int `toml:"max_test_ips"`
109+
// CacheTTL is the preference cache TTL in seconds. 0 means use DNS TTL / 2.
110+
CacheTTL int `toml:"cache_ttl"`
111+
// CacheSize limits the number of entries in the preference cache. 0 = unlimited.
112+
CacheSize int `toml:"cache_size"`
83113
}
84114

85115
// TimeoutConfig contains timeout settings in seconds.

internal/config/config.toml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,65 @@
8787
# 引导 DNS 服务器,用于解析上述加密 DNS 服务器自身的域名。
8888
# bootstrap_dns = ["tls://223.5.5.5"]
8989

90+
# [DNS IP Preference]
91+
# Controls how Snirect selects between IPv6 and IPv4 addresses when both are available.
92+
# Requires ipv6 = true to function (IPv6 must be enabled).
93+
#
94+
# DNS IP 优选策略
95+
# 控制 Snirect 在有 IPv6 和 IPv4 地址时如何选择。
96+
# 需要先开启 ipv6 = true 才会生效。
97+
[preference]
98+
# Mode: standard, fastest, ipv6, ipv4
99+
# standard - (Default) Prefer IPv6 if available, otherwise first available
100+
# fastest - Test connection latency to all IPs and use the fastest (cached)
101+
# ipv6 - Always prefer IPv6 when available (no testing)
102+
# ipv4 - Force IPv4 only
103+
#
104+
# 模式: standard (标准), fastest (最快), ipv6 (强制v6), ipv4 (强制v4)
105+
mode = "standard"
106+
107+
# When mode="fastest", whether to actually test latency.
108+
# If false, behaves like "standard" (no testing overhead).
109+
#
110+
# 在 fastest 模式下是否实际进行延迟测试。
111+
# 若为 false,则退化为 standard 模式(无测试开销)。
112+
enable_testing = true
113+
114+
# Timeout for each connection test in milliseconds.
115+
# Lower = faster fallback, higher = more accurate.
116+
# 每个连接测试的超时时间(毫秒)。
117+
test_timeout_ms = 500
118+
119+
# Test multiple IPs in parallel (true) or sequentially (false).
120+
# Parallel is faster but uses more resources.
121+
#
122+
# 并行 (true) 或 顺序 (false) 测试多个 IP。
123+
# 并行更快但占用更多资源。
124+
test_parallel = true
125+
126+
# Maximum number of IPs to test per query.
127+
# Set to conserve resources when many IPs are available (e.g., CDNs).
128+
#
129+
# 每次查询最多测试的 IP 数量。
130+
# 当可用 IP 很多时(如 CDN)可限制资源消耗。
131+
max_test_ips = 10
132+
133+
# Preference cache TTL in seconds.
134+
# 0 = automatically use half of the DNS record's TTL.
135+
# Shorter TTL = adapt to network changes faster.
136+
#
137+
# 优选结果缓存时间(秒)。
138+
# 0 = 自动使用 DNS 记录 TTL 的一半。
139+
# 较短的 TTL 能更快适应网络变化。
140+
cache_ttl = 300
141+
142+
# Maximum entries in preference cache (0 = unlimited).
143+
# PrefCache is separate from DNS cache and usually much smaller.
144+
#
145+
# 优选缓存的最大条目数 (0 = 不限制)。
146+
# 优选缓存独立于 DNS 缓存,通常小得多。
147+
cache_size = 5000
148+
90149
# [Timeout Settings]
91150
# Timeouts in seconds.
92151
#

internal/config/defaults.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import ruleslib "github.com/xihale/snirect-shared/rules"
99
//go:embed config.default.toml
1010
var DefaultConfigTOML string
1111

12-
var DefaultRulesTOML = ruleslib.FetchedRulesTOML
12+
var DefaultRulesTOML = ruleslib.DefaultRulesTOML
1313

1414
//go:embed config.toml
1515
var SampleConfigTOML string
1616

17-
var SampleRulesTOML = ruleslib.UserRulesTOML
17+
// SampleRulesTOML is not available in the shared rules package; leave empty.
18+
var SampleRulesTOML = ""
1819

1920
//go:embed pac
2021
var DefaultPAC string

internal/config/tools/convert_rules/main.go

Lines changed: 0 additions & 92 deletions
This file was deleted.

internal/config/tools/gendefaults/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func run() error {
8484
return fmt.Errorf("failed to parse config: %w", err)
8585
}
8686

87-
rulesData := []byte(ruleslib.FetchedRulesTOML)
87+
rulesData := []byte(ruleslib.DefaultRulesTOML)
8888

8989
var rules Rules
9090
if err := toml.Unmarshal(rulesData, &rules); err != nil {

0 commit comments

Comments
 (0)