问题描述
DohClient(src/engine/transport.rs)与 TcpMuxClient 相比,缺少连接自愈机制:当代理节点抖动导致 DoH 连接失效后,连接池中的死连接被无限复用,每次请求 3 秒超时并反复重试,日志持续刷屏,只能通过重启 kixdns 恢复。
对比(同文件内实现差异)
TcpMuxClient(有完整自愈)
- 透明重试:复用连接失败时,用新连接重试一次(
send() 中 is_reused 检查 → send_attempt 失败 → 新建连接重试,预留至少 1.5s 预算)
- 错误计数重置:
record_error() 连续错误 ≥ 阈值 → reset() 连接并清零计数
- 连接老化:
check_connection_health() 检查 max_age → 超龄 reset
- 空闲超时:
idle_timeout → 超时 reset
- 健康检查:30 秒间隔执行
DohClient(当前实现,transport.rs 1218-1271 行)
pub async fn send(&self, packet, upstream, timeout_dur) -> anyhow::Result<Bytes> {
// reqwest client 复用(pool_idle_timeout=90s, pool_max_idle_per_host)
let bytes = timeout(timeout_dur, async {
let resp = req.send().await.context("doh request send failed")?;
// ... 读响应体
}).await.context("doh request timeout")??;
Ok(bytes)
}
- ❌ 无透明重试(复用连接失败直接返回错误)
- ❌ 无错误计数/连接重置
- ❌ 无连接老化检查
- ⚠️ 仅
pool_idle_timeout=90s:但后台刷新(background refresh)每 ~10 秒触发一次 DoH 请求,连接永不空闲,此超时几乎不生效
实际影响(实测复现)
kixdns e521751c5a(main 2026-08-11),代理节点(dae HK 组)抖动期间:
upstream call failed, waiting for others upstream=doh:8.8.8.8/dns-query error=doh request timeout elapsed_ns=3001xxx
upstream call failed, waiting for others upstream=doh:1.1.1.1/dns-query error=doh request timeout elapsed_ns=3001xxx
- 触发域名:
www.google.com、oauth2.googleapis.com(谷歌系,走 doh-fallback)
- 频率:每 ~10 秒一对,持续 3+ 小时(1396 条)
- 节点抖动恢复后仍持续刷屏(连接池死连接未清理),重启 kixdns 才恢复
- 手动 curl(每次都新建连接)在节点恢复后立即成功——证实是连接池复用问题,非网络问题
期望行为
DohClient 具备与 TcpMuxClient 对等的自愈能力:
- 透明重试:复用连接请求失败(传输错误/超时)时,用新连接重试一次
- 连接池重置:连续错误超过阈值时重建 reqwest Client(等价于 reset 连接池)
修复建议
请上游自行评估实现方案。
参考线索:
TcpMuxClient::send()(transport.rs 800-850 行)的透明重试模式可直接借鉴
- reqwest 连接池无法单独销毁某条连接,可考虑错误计数达到阈值时整体重建
DohHttpClient(DohClient::new() 已封装构建逻辑)
- 或调整
pool_idle_timeout 与 http2_adaptive_window 等参数缓解(不治本)
环境
- kixdns
e521751c5a(main,2026-08-11)
- dae 代理(
dip(8.8.8.8, 1.1.1.1) -> HK),DoH 经代理访问 8.8.8.8/1.1.1.1(直连不可行:UDP 53 被污染、TCP 53/443 被墙)
- 触发场景:代理节点抖动 +
cache_background_refresh: true
问题描述
DohClient(src/engine/transport.rs)与TcpMuxClient相比,缺少连接自愈机制:当代理节点抖动导致 DoH 连接失效后,连接池中的死连接被无限复用,每次请求 3 秒超时并反复重试,日志持续刷屏,只能通过重启 kixdns 恢复。对比(同文件内实现差异)
TcpMuxClient(有完整自愈)
send()中is_reused检查 →send_attempt失败 → 新建连接重试,预留至少 1.5s 预算)record_error()连续错误 ≥ 阈值 →reset()连接并清零计数check_connection_health()检查max_age→ 超龄 resetidle_timeout→ 超时 resetDohClient(当前实现,transport.rs 1218-1271 行)
pool_idle_timeout=90s:但后台刷新(background refresh)每 ~10 秒触发一次 DoH 请求,连接永不空闲,此超时几乎不生效实际影响(实测复现)
kixdns
e521751c5a(main 2026-08-11),代理节点(dae HK 组)抖动期间:www.google.com、oauth2.googleapis.com(谷歌系,走 doh-fallback)期望行为
DohClient具备与TcpMuxClient对等的自愈能力:修复建议
请上游自行评估实现方案。
参考线索:
TcpMuxClient::send()(transport.rs 800-850 行)的透明重试模式可直接借鉴DohHttpClient(DohClient::new()已封装构建逻辑)pool_idle_timeout与http2_adaptive_window等参数缓解(不治本)环境
e521751c5a(main,2026-08-11)dip(8.8.8.8, 1.1.1.1) -> HK),DoH 经代理访问 8.8.8.8/1.1.1.1(直连不可行:UDP 53 被污染、TCP 53/443 被墙)cache_background_refresh: true