Skip to content

Commit d6eee4c

Browse files
authored
Merge pull request #9 from MaurUppi/dns_fix
feat(dns): configurable DNS ingress performance levels
2 parents c03d13a + 3a308b9 commit d6eee4c

File tree

7 files changed

+275
-39
lines changed

7 files changed

+275
-39
lines changed

.plan/test-log.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,113 @@ rg -n "TestAnyfromPoolGetOrCreate_ZeroTTLStillPooled|TestAnyfromPoolGetOrCreate_
520520
1. 修复已按 High -> Medium 串行落地(F1/F3/F4/F2)。
521521
2. 本地可执行代码级验证通过。
522522
3. 编译/运行级回归受 Linux + eBPF 环境限制,需 PR 触发 CI 完成最终闭环。
523+
524+
## dns-traceback-3rd-fix T10: DNS ingress 分级可配置化(T1 -> T6 -> M1)
525+
526+
**日期**: 2026-02-18
527+
**来源**: `/Users/ouzy/Documents/DevProjects/dae/.plan/code_audit_trace-back-3rd.md`
528+
**执行文档**: `/Users/ouzy/Documents/DevProjects/dae/.plan/code_audit_trace-back-3rd-dev.md`
529+
530+
### T1(config/config.go)新增 dns ingress 配置结构与字段
531+
532+
**变更文件**
533+
- `config/config.go`
534+
535+
**测试命令与结果**
536+
```bash
537+
rg -n "DnsIngressManual|DnsPerformanceLevel|dns_performance_level|dns_ingress_manual" config/config.go
538+
→ PASS: 命中新类型与 Global 新字段
539+
```
540+
541+
### T2(config/patch.go)新增 level 校验与 manual clamp
542+
543+
**变更文件**
544+
- `config/patch.go`
545+
546+
**测试命令与结果**
547+
```bash
548+
rg -n "patchDnsPerformanceLevel|dns_performance_level|dns_ingress_manual" config/patch.go
549+
→ PASS: 命中 patch 注册、fallback、workers/queue clamp 警告
550+
```
551+
552+
### T3(config/desc.go)补充描述文本
553+
554+
**变更文件**
555+
- `config/desc.go`
556+
557+
**测试命令与结果**
558+
```bash
559+
rg -n "dns_performance_level" config/desc.go
560+
→ PASS: 命中 GlobalDesc 说明
561+
```
562+
563+
### T4(control/control_plane.go)profile 查找表与初始化改造
564+
565+
**变更文件**
566+
- `control/control_plane.go`
567+
568+
**测试命令与结果**
569+
```bash
570+
rg -n "dnsIngressProfile|resolveDnsIngressProfile|dnsIngressWorkerCount|DNS ingress: level" control/control_plane.go
571+
→ PASS: 命中 profile、解析函数、worker 计数与启动日志
572+
```
573+
574+
### T5(example.dae)补充示例配置
575+
576+
**变更文件**
577+
- `example.dae`
578+
579+
**测试命令与结果**
580+
```bash
581+
rg -n "dns_performance_level|dns_ingress_manual" example.dae
582+
→ PASS: 命中 level 与 manual 示例注释
583+
```
584+
585+
### T6(control/dns_improvement_test.go)新增 profile 解析测试
586+
587+
**变更文件**
588+
- `control/dns_improvement_test.go`
589+
590+
**测试命令与结果**
591+
```bash
592+
rg -n "TestResolveDnsIngressProfile" control/dns_improvement_test.go
593+
→ PASS: 命中新增测试函数
594+
```
595+
596+
### M1(本地里程碑验证)
597+
598+
**执行命令与结果**
599+
```bash
600+
# 1) 格式化
601+
gofmt -w config/config.go config/patch.go config/desc.go control/control_plane.go control/dns_improvement_test.go
602+
→ PASS
603+
604+
# 2) 默认 go.work(环境检查)
605+
go test ./config -run TestPatchDnsPerformanceLevel -count=1
606+
→ FAIL: go.work 引用了本机缺失模块 ../cloudpan189-go
607+
608+
# 3) 关闭 go.work 的 config 包编译检查
609+
GOWORK=off go test ./config -run TestPatchDnsPerformanceLevel -count=1
610+
→ PASS (no tests to run, 编译通过)
611+
612+
# 4) 关闭 go.work 的 config 运行级回归
613+
GOWORK=off go test ./config -count=1
614+
→ FAIL: TestMarshal 要求 example.dae 文件权限 <=0640,本机检出为 0644(历史环境约束)
615+
616+
# 5) 关闭 go.work 的 control 包测试(darwin)
617+
GOWORK=off go test ./control -run TestResolveDnsIngressProfile -count=1
618+
→ FAIL: 缺失 Linux netlink/IP_TRANSPARENT 常量(平台限制)
619+
620+
# 6) Linux 目标的 control 包编译尝试
621+
GOWORK=off GOOS=linux GOARCH=amd64 go test ./control -run TestNoSuch -count=1
622+
→ FAIL: 缺失 bpfObjects/bpfRoutingResult(需 CI eBPF 生成链路)
623+
624+
# 7) config 包 vet 检查(Linux 目标)
625+
GOWORK=off GOOS=linux GOARCH=amd64 go vet ./config/
626+
→ FAIL: config/marshal.go 与 config/parser.go 现存 unreachable code(与本次改动无关)
627+
```
628+
629+
**结论**
630+
1. T1~T6 代码改动与结构验证全部完成。
631+
2. 本地受 go.work、darwin/Linux 差异、eBPF 生成链路限制,无法完成 control 包运行级回归。
632+
3. 下一步需通过 PR 触发 CI(Linux runner)完成编译/测试闭环。

config/config.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ var (
1717
Version string
1818
)
1919

20+
type DnsIngressManual struct {
21+
Workers uint16 `mapstructure:"workers" default:"0"`
22+
Queue uint16 `mapstructure:"queue" default:"0"`
23+
}
24+
2025
type Global struct {
2126
TproxyPort uint16 `mapstructure:"tproxy_port" default:"12345"`
2227
TproxyPortProtect bool `mapstructure:"tproxy_port_protect" default:"true"`
@@ -37,15 +42,17 @@ type Global struct {
3742
EnableLocalTcpFastRedirect bool `mapstructure:"enable_local_tcp_fast_redirect" default:"false"`
3843
AutoConfigKernelParameter bool `mapstructure:"auto_config_kernel_parameter" default:"false"`
3944
// DEPRECATED: not used as of https://github.com/daeuniverse/dae/pull/458
40-
AutoConfigFirewallRule bool `mapstructure:"auto_config_firewall_rule" default:"false"`
41-
SniffingTimeout time.Duration `mapstructure:"sniffing_timeout" default:"100ms"`
42-
TlsImplementation string `mapstructure:"tls_implementation" default:"tls"`
43-
UtlsImitate string `mapstructure:"utls_imitate" default:"chrome_auto"`
44-
PprofPort uint16 `mapstructure:"pprof_port" default:"0"`
45-
Mptcp bool `mapstructure:"mptcp" default:"false"`
46-
FallbackResolver string `mapstructure:"fallback_resolver" default:"8.8.8.8:53"`
47-
BandwidthMaxTx string `mapstructure:"bandwidth_max_tx" default:"0"`
48-
BandwidthMaxRx string `mapstructure:"bandwidth_max_rx" default:"0"`
45+
AutoConfigFirewallRule bool `mapstructure:"auto_config_firewall_rule" default:"false"`
46+
SniffingTimeout time.Duration `mapstructure:"sniffing_timeout" default:"100ms"`
47+
TlsImplementation string `mapstructure:"tls_implementation" default:"tls"`
48+
UtlsImitate string `mapstructure:"utls_imitate" default:"chrome_auto"`
49+
PprofPort uint16 `mapstructure:"pprof_port" default:"0"`
50+
Mptcp bool `mapstructure:"mptcp" default:"false"`
51+
FallbackResolver string `mapstructure:"fallback_resolver" default:"8.8.8.8:53"`
52+
BandwidthMaxTx string `mapstructure:"bandwidth_max_tx" default:"0"`
53+
BandwidthMaxRx string `mapstructure:"bandwidth_max_rx" default:"0"`
54+
DnsPerformanceLevel string `mapstructure:"dns_performance_level" default:"balanced"`
55+
DnsIngressManual DnsIngressManual `mapstructure:"dns_ingress_manual"`
4956
}
5057

5158
type Utls struct {

config/desc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ var GlobalDesc = Desc{
5858
"tls_implementation": "TLS implementation. \"tls\" is to use Go's crypto/tls. \"utls\" is to use uTLS, which can imitate browser's Client Hello.",
5959
"utls_imitate": "The Client Hello ID for uTLS to imitate. This takes effect only if tls_implementation is utls. See more: https://github.com/daeuniverse/dae/blob/331fa23c16/component/outbound/transport/tls/utls.go#L17",
6060
"mptcp": "Enable Multipath TCP. If is true, dae will try to use MPTCP to connect all nodes, but it will only take effects when the node supports MPTCP. It can use for load balance and failover to multiple interfaces and IPs.",
61+
"dns_performance_level": "DNS ingress performance level. Options: lean, balanced (default), aggressive, manual. " +
62+
"Use 'lean' for low-power devices, 'aggressive' for high-load environments. " +
63+
"Only set 'manual' if you need fine-grained control over worker and queue sizes.",
6164
}
6265

6366
var DnsDesc = Desc{

config/patch.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type patch func(params *Config) error
1919
var patches = []patch{
2020
patchTcpCheckHttpMethod,
2121
patchEmptyDns,
22+
patchDnsPerformanceLevel,
2223
patchMustOutbound,
2324
}
2425

@@ -40,6 +41,46 @@ func patchEmptyDns(params *Config) error {
4041
return nil
4142
}
4243

44+
func patchDnsPerformanceLevel(params *Config) error {
45+
level := strings.ToLower(strings.TrimSpace(params.Global.DnsPerformanceLevel))
46+
switch level {
47+
case "lean", "balanced", "aggressive", "manual":
48+
params.Global.DnsPerformanceLevel = level
49+
case "":
50+
params.Global.DnsPerformanceLevel = "balanced"
51+
default:
52+
logrus.Warnf("Unknown dns_performance_level '%s', falling back to 'balanced'", params.Global.DnsPerformanceLevel)
53+
params.Global.DnsPerformanceLevel = "balanced"
54+
}
55+
56+
if params.Global.DnsPerformanceLevel == "manual" {
57+
m := &params.Global.DnsIngressManual
58+
const minW, maxW uint16 = 32, 1024
59+
const minQ, maxQ uint16 = 128, 16384
60+
if m.Workers == 0 {
61+
m.Workers = 256
62+
}
63+
if m.Queue == 0 {
64+
m.Queue = 2048
65+
}
66+
if m.Workers < minW {
67+
logrus.Warnf("dns_ingress_manual.workers %d below min %d, clamping", m.Workers, minW)
68+
m.Workers = minW
69+
} else if m.Workers > maxW {
70+
logrus.Warnf("dns_ingress_manual.workers %d above max %d, clamping", m.Workers, maxW)
71+
m.Workers = maxW
72+
}
73+
if m.Queue < minQ {
74+
logrus.Warnf("dns_ingress_manual.queue %d below min %d, clamping", m.Queue, minQ)
75+
m.Queue = minQ
76+
} else if m.Queue > maxQ {
77+
logrus.Warnf("dns_ingress_manual.queue %d above max %d, clamping", m.Queue, maxQ)
78+
m.Queue = maxQ
79+
}
80+
}
81+
return nil
82+
}
83+
4384
func patchMustOutbound(params *Config) error {
4485
for i := range params.Routing.Rules {
4586
if strings.HasPrefix(params.Routing.Rules[i].Outbound.Name, "must_") {

control/control_plane.go

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,33 @@ import (
4848

4949
const (
5050
// DNS packets are handled by dedicated workers instead of per-src queue.
51-
dnsIngressWorkerCount = 256
52-
dnsIngressQueueLength = 2048
5351
dnsIngressQueueLogEvery = 100
5452
)
5553

54+
type dnsIngressProfile struct {
55+
workers int
56+
queueLen int
57+
}
58+
59+
var dnsIngressProfiles = map[string]dnsIngressProfile{
60+
"lean": {workers: 32, queueLen: 128},
61+
"balanced": {workers: 256, queueLen: 2048},
62+
"aggressive": {workers: 1024, queueLen: 4096},
63+
}
64+
65+
func resolveDnsIngressProfile(level string, manual config.DnsIngressManual) dnsIngressProfile {
66+
if level == "manual" {
67+
return dnsIngressProfile{
68+
workers: int(manual.Workers),
69+
queueLen: int(manual.Queue),
70+
}
71+
}
72+
if p, ok := dnsIngressProfiles[level]; ok {
73+
return p
74+
}
75+
return dnsIngressProfiles["balanced"]
76+
}
77+
5678
type dnsIngressTask struct {
5779
data pool.PB
5880
convergeSrc netip.AddrPort
@@ -72,10 +94,11 @@ type ControlPlane struct {
7294
outbounds []*outbound.DialerGroup
7395
inConnections sync.Map
7496

75-
dnsController *DnsController
76-
onceNetworkReady sync.Once
77-
dnsIngressQueue chan dnsIngressTask
78-
emitUdpTask func(key string, task UdpTask)
97+
dnsController *DnsController
98+
onceNetworkReady sync.Once
99+
dnsIngressQueue chan dnsIngressTask
100+
dnsIngressWorkerCount int
101+
emitUdpTask func(key string, task UdpTask)
79102
// dnsIngressQueueFullTotal tracks how many DNS packets hit full lane queue.
80103
dnsIngressQueueFullTotal uint64
81104
// dnsIngressDropTotal tracks dropped DNS packets at ingress (queue full).
@@ -404,30 +427,34 @@ func NewControlPlane(
404427

405428
// New control plane.
406429
ctx, cancel := context.WithCancel(context.Background())
430+
dnsIngressCfg := resolveDnsIngressProfile(global.DnsPerformanceLevel, global.DnsIngressManual)
407431
plane := &ControlPlane{
408-
log: log,
409-
core: core,
410-
deferFuncs: deferFuncs,
411-
listenIp: "0.0.0.0",
412-
outbounds: outbounds,
413-
dnsController: nil,
414-
onceNetworkReady: sync.Once{},
415-
dnsIngressQueue: make(chan dnsIngressTask, dnsIngressQueueLength),
416-
emitUdpTask: DefaultUdpTaskPool.EmitTask,
417-
dialMode: dialMode,
418-
routingMatcher: routingMatcher,
419-
ctx: ctx,
420-
cancel: cancel,
421-
ready: make(chan struct{}),
422-
muRealDomainSet: sync.Mutex{},
423-
realDomainSet: bloom.NewWithEstimates(2048, 0.001),
424-
lanInterface: global.LanInterface,
425-
wanInterface: global.WanInterface,
426-
sniffingTimeout: sniffingTimeout,
427-
tproxyPortProtect: global.TproxyPortProtect,
428-
soMarkFromDae: global.SoMarkFromDae,
429-
mptcp: global.Mptcp,
430-
}
432+
log: log,
433+
core: core,
434+
deferFuncs: deferFuncs,
435+
listenIp: "0.0.0.0",
436+
outbounds: outbounds,
437+
dnsController: nil,
438+
onceNetworkReady: sync.Once{},
439+
dnsIngressQueue: make(chan dnsIngressTask, dnsIngressCfg.queueLen),
440+
dnsIngressWorkerCount: dnsIngressCfg.workers,
441+
emitUdpTask: DefaultUdpTaskPool.EmitTask,
442+
dialMode: dialMode,
443+
routingMatcher: routingMatcher,
444+
ctx: ctx,
445+
cancel: cancel,
446+
ready: make(chan struct{}),
447+
muRealDomainSet: sync.Mutex{},
448+
realDomainSet: bloom.NewWithEstimates(2048, 0.001),
449+
lanInterface: global.LanInterface,
450+
wanInterface: global.WanInterface,
451+
sniffingTimeout: sniffingTimeout,
452+
tproxyPortProtect: global.TproxyPortProtect,
453+
soMarkFromDae: global.SoMarkFromDae,
454+
mptcp: global.Mptcp,
455+
}
456+
log.Infof("DNS ingress: level=%s, workers=%d, queue_len=%d",
457+
global.DnsPerformanceLevel, dnsIngressCfg.workers, dnsIngressCfg.queueLen)
431458
defer func() {
432459
if err != nil {
433460
cancel()
@@ -816,7 +843,7 @@ func (c *ControlPlane) dispatchDnsOrQueue(convergeSrc, pktDst netip.AddrPort, dn
816843
}
817844

818845
func (c *ControlPlane) startDnsIngressWorkers(udpConn *net.UDPConn) {
819-
for i := 0; i < dnsIngressWorkerCount; i++ {
846+
for i := 0; i < c.dnsIngressWorkerCount; i++ {
820847
go func() {
821848
for {
822849
select {

control/dns_improvement_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,39 @@ func TestDrainDnsIngressQueue_DrainsWithoutCountingDrop(t *testing.T) {
348348
}
349349
}
350350

351+
func TestResolveDnsIngressProfile(t *testing.T) {
352+
tests := []struct {
353+
name string
354+
level string
355+
manual config.DnsIngressManual
356+
workers int
357+
queue int
358+
}{
359+
{name: "lean", level: "lean", workers: 32, queue: 128},
360+
{name: "balanced", level: "balanced", workers: 256, queue: 2048},
361+
{name: "aggressive", level: "aggressive", workers: 1024, queue: 4096},
362+
{
363+
name: "manual",
364+
level: "manual",
365+
manual: config.DnsIngressManual{Workers: 512, Queue: 4096},
366+
workers: 512,
367+
queue: 4096,
368+
},
369+
{name: "unknown", level: "unknown", workers: 256, queue: 2048},
370+
{name: "empty", level: "", workers: 256, queue: 2048},
371+
}
372+
373+
for _, tt := range tests {
374+
t.Run(tt.name, func(t *testing.T) {
375+
got := resolveDnsIngressProfile(tt.level, tt.manual)
376+
if got.workers != tt.workers || got.queueLen != tt.queue {
377+
t.Fatalf("resolveDnsIngressProfile(%q, %+v) = {%d, %d}, want {%d, %d}",
378+
tt.level, tt.manual, got.workers, got.queueLen, tt.workers, tt.queue)
379+
}
380+
})
381+
}
382+
}
383+
351384
func TestAnyfromPoolGetOrCreate_ZeroTTLStillPooled(t *testing.T) {
352385
p := NewAnyfromPool()
353386
var createCalls atomic.Int32

example.dae

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ global {
1212
# Set non-zero value to enable pprof.
1313
pprof_port: 0
1414

15+
# DNS ingress performance level.
16+
# Options: lean, balanced (default), aggressive, manual.
17+
# - lean: for low-power/embedded devices (32 workers)
18+
# - balanced: for typical home/small office (256 workers)
19+
# - aggressive: for high-load environments (1024 workers)
20+
# - manual: expert tuning, requires dns_ingress_manual section below
21+
# dns_performance_level: balanced
22+
23+
# Only effective when dns_performance_level is "manual".
24+
# Workers: 32-1024, Queue: 128-16384.
25+
# dns_ingress_manual {
26+
# workers: 512
27+
# queue: 2048
28+
# }
29+
1530
# If not zero, traffic sent from dae will be set SO_MARK. It is useful to avoid traffic loop with iptables tproxy
1631
# rules.
1732
so_mark_from_dae: 0

0 commit comments

Comments
 (0)