Turso 连接故障排查:DNS 污染、TCP 阻断与 TLS 证书劫持

一次本地开发环境中 @libsql/client 间歇性 fetch failed 的完整诊断记录,涵盖 DNS 污染识别、TLS 中间人攻击特征判断及多层代理下的正确配置方式。

症状

后端使用 Turso(libsql)作为远端数据库。生产环境部署在阿里云 ECS 上,一切正常;本地 Windows 开发机启动后端后,偶发性出现以下错误:

code
TypeError: fetch failedcaused by: Error: Client network socket disconnectedbefore secure TLS connection was established

初期表现为间歇性,约 20–30 分钟出现一次;后期彻底不可用,每次请求均失败。

诊断过程

第一层:Ping 与 DNS

powershell
ping xxxxx.aws-ap-northeast-1.turso.io

Ping 返回 <1ms,IP 为 198.18.0.246 —— 该地址属于 RFC 2544 定义的 Benchmarking 保留段(198.18.0.0/15),不可能是 AWS 东京区 Turso 实例的真实 IP。DNS 已被劫持。

用 Google DNS 交叉验证:

powershell
nslookup xxxxx.aws-ap-northeast-1.turso.io 8.8.8.8

返回真实 IP 54.178.40.18(AWS ELB, ap-northeast-1),证实运营商 DNS 存在污染。

第二层:TLS 握手

直接对污染返回的假 IP 发起 HTTPS 请求:

powershell
curl https://xxxxx.aws-ap-northeast-1.turso.io

错误信息:

code
schannel: SNI or certificate check failed: SEC_E_WRONG_PRINCIPAL

或者 Node.js 端更详细的报错:

code
ERR_TLS_CERT_ALTNAME_INVALID:Host: xxxxx.aws-ap-northeast-1.turso.iois not in the cert's altnames: DNS:dev01.m-standard.co.jp, DNS:*.dev01.m-standard.co.jp

证书中 SAN 指向的是 m-standard.co.jp(一家日本企业),与 Turso 完全无关。这是典型的 DNS 投毒 + TCP 劫持:DNS 污染返回一个中间人的 IP,该 IP 回应了带有自签名或第三方证书的 TLS 握手,目标主机的真实 IP 在 TCP 层就被阻断。

第三层:网络路径

将 DNS 修复为 Google DNS(8.8.8.8)后,nslookup 返回了正确 IP 54.178.40.18,但直连该 IP 的 TCP 443 端口仍然不通:

code
Direct:  HTTP 000, Time: 0.28s   ← TCP RST,未进入 TLS 握手

同一时刻通过 Clash 代理(--proxy http://127.0.0.1:7897)连接:

code
Proxy:  HTTP 401, Time: 0.48s   ← TLS 握手成功,返回 401 仅是因为未携带认证 token

结论:GFW 在运营商骨干网的出口处不仅污染 DNS,还阻断了 AWS 东京区 Turso 实例的 TCP 443 端口。

为什么生产服务器没问题

云服务商(阿里云 ECS)的数据中心通过 BGP 专线与国际网络互联,流量不经过 GFW 的拦截节点。其 DNS 不受污染,TCP 连接不受阻断,因此 Turso 直连完全正常。

本地家用宽带的出口路径则必然经过 GFW 设备,运营商提供的 DNS 默认已被投毒,且特定 IP 段的 TCP 443 被选择性阻断。

解决方案

方案一:TUN 模式代理(推荐)

Clash 等代理工具的 TUN 模式通过创建虚拟网卡接管系统所有流量(包括 DNS 和 TCP),将全部数据包通过加密隧道转发,彻底绕过 GFW 的检测与阻断。无需修改任何应用层配置,对 @libsql/client 完全透明。

方案二:系统代理 + DNS 修复

  1. DNS 更换为 Google(8.8.8.8)或 Cloudflare(1.1.1.1
  2. 系统代理指向 Clash(127.0.0.1:7897
  3. 启动后端时显式设置 HTTPS_PROXY 环境变量:
powershell
$env:HTTPS_PROXY = 'http://127.0.0.1:7897'npm run dev

注意:Node.js 的 fetch API 不读取 Windows 系统代理设置,必须通过环境变量 HTTPS_PROXY(大小写不敏感)显式指定。npm run devnode --watch 子进程会继承该变量。

方案三:Hosts 强制解析

C:\Windows\System32\drivers\etc\hosts 中添加:

code
54.178.40.18 xxxxx.aws-ap-northeast-1.turso.io

仅解决 DNS 层面的问题,但无法绕过 TCP 阻断,不适用于当前场景


排查方法论小结

对于 “fetch failed / TLS handshake failed” 类远程服务连接问题,可遵循以下流水线:

  1. DNS 验证nslookup <host> <trusted_dns> 对比运营商 DNS 与可信 DNS 的解析结果,判断是否存在投毒
  2. TCP 可达性curl --connect-timeout <host>:443 测试原始 TCP 连接是否被阻断
  3. TLS 证书检查 — 建立 TLS 连接后核对 SAN 字段,确认是否遭遇中间人劫持
  4. 代理对比 — 同一条请求分别走直连和代理,若代理正常而直连失败,则判定为网络层阻断
  5. 环境差异排除 — 对比本地(家宽)与云端(数据中心)的行为差异,明确问题是否出在出口路径

这套流程可以在一两分钟内定位到问题所在的网络分层(DNS → TCP → TLS),避免在应用代码层面浪费排查时间。