Netpath
学习路径6 / 49 · 科学上网与隧道看全程 →
闯关预计 30 分钟

闯关:隧道昨天还好,今天不通了

在模拟终端里从「浏览器打不开」一路查到具体哪一跳断了。

学完这节你能做到

  • 按客户端 → 本地监听 → 隧道 → 出口逐段定位
  • 区分 DNS 失败、监听没起、隧道断开与出口被封
  • 给出可验证的结论与恢复步骤

建议先学

跳过这几节会看不懂本节的部分推导

接手

自己的开发机,今天早上开始出问题:

浏览器打不开 GitHub,git push 卡住,pip install 超时。昨天下班前还好的。 隧道进程看着是活的。

已知信息:

  • 本地 Mihomo 监听 mixed-port 7892,出口是自建的 anytls 服务器 proxy.example.com:443
  • 昨晚没改过任何配置
  • 公司内网服务访问正常
✓怎么玩

按 L5 的排障顺序:本地监听 → 隧道连接 → 出口可达 → 目标可达。 输入 goals 看目标,hint 要提示。四个目标全达成即过关。

"进程还活着"这句话不能当证据用 —— 想想上一节讲的 ExitOnForwardFailure。

wutz@devbox目标 0/4
  1. 1.确认本地代理端口是否还在监听
  2. 2.确认问题出在代理链路而不是本机 DNS 或系统代理设置
  3. 3.从日志确认代理到出口的连接失败在哪一步
  4. 4.找出根因:确认域名解析到的地址是否与实际使用的一致
浏览器打不开 GitHub、git push 卡住、pip 超时。
本地 Mihomo 在 7892,出口 proxy.example.com:443。昨晚未改配置。
[wutz@devbox ~]#
help 查看用法 · goals 看目标 · hint 要提示 · ↑↓ 翻历史

结论应该长什么样

证据说明
curl 超时而非 refused包没到目标,或到了没回音
ss -lntp 端口在监听本地这一跳正常
显式指定代理后报 SOCKS5 连接失败本地代理在工作,问题在它的上游
/connections 显示规则与节点都命中正确排除规则写错、选组错误
日志:dial tcp 203.0.113.77:443: i/o timeout拿到了它实际在连的 IP
nc 连 .77 超时那个地址确实不通
dig 解析出 .201,与日志里的 .77 不一致根因
nc 连 .201 成功、ping 正常出口服务本身健康
进程从昨天未重启解释了为什么它还拿着旧地址

根因:出口服务器的公网 IP 变了(VPS 迁移或弹性 IP 重新分配), DNS 已经更新到新地址,但长期运行的 Mihomo 进程缓存了旧的解析结果, 一直在往已经不存在的旧 IP 上建连。

修法:

# 立刻恢复:重启核心让它重新解析
# (Mihomo 也可以通过 API 触发配置重载)
curl -X PUT http://127.0.0.1:9090/configs -d '{"path":"","payload":""}'
# 或者直接重启服务
launchctl kickstart -k gui/$(id -u)/mihomo     # macOS
systemctl --user restart mihomo                # Linux

# 验证
curl -m 5 -sS -x socks5h://127.0.0.1:7892 https://github.com -o /dev/null -w '%{http_code}\n'

根治(按有效性排序):

  1. 服务端用弹性/静态 IP,别让它随重建变化
  2. 客户端配置里用域名而不是 IP(这次已经是域名,问题在缓存)
  3. 给节点配健康检查,让 fallback 组能自动切走
  4. 定期重启或重载长期运行的代理进程
×「进程还活着」不能当证据

这一关和 SSH 端口转发 那节的 ExitOnForwardFailure 是同一类问题:

进程在跑 ≠ 链路可用。 长期运行的隧道进程有三种"假活"状态:

假活形态表现
缓存了旧的 DNS 解析本例
端口绑定失败但进程未退出ssh 不加 ExitOnForwardFailure
底层连接已断但未检测到缺少保活探测

所以健康检查要探测端到端可用性,而不是看进程存活:

# 一条能进 cron 的健康检查
curl -m 8 -s -o /dev/null -w '%{http_code}' \
  -x socks5h://127.0.0.1:7892 https://www.gstatic.com/generate_204 \
  | grep -q 204 || echo "proxy broken at $(date)"
i这一关的通用方法:从近到远逐跳确认

隧道类故障的路径很长,但排查顺序是固定的:

① 本地监听在不在       ss -lntp | grep <port>
② 本地代理是否响应     curl -x <本地代理> <目标>
③ 规则与节点选对没     /connections 的 rule 与 chains
④ 代理到出口通不通     日志里的 dial 结果 + nc 直连出口
⑤ 出口地址对不对       dig 与日志里的 IP 对比      ← 本例的根因
⑥ 出口到目标通不通     在出口服务器上验证

每一步都只确认一件事,确认完就往下走一跳。 乱序排查(比如一上来就重装客户端)会让你丢掉现场,还找不到原因。

检查点

Checkpoint单选

curl 报超时而不是 connection refused,这个区别说明什么?

Checkpoint单选

代理日志显示 dial tcp 203.0.113.77:443: i/o timeout,而 dig 解析出的是 203.0.113.201。说明什么?

Checkpoint单选

为什么健康检查不能只看进程是否存活?

这一关的落点

  • 排障顺序固定:本地监听 → 本地代理响应 → 规则与节点 → 代理到出口 → 出口地址 → 出口到目标
  • timeout 与 refused 方向相反:前者包没到,后者到了被拒
  • 显式 curl -x <本地代理> 能一步把"系统代理设置"这个变量摘掉
  • /connections 的 rule 与 chains 两个字段可以排除掉最常见的自己作的问题
  • 把日志里实际在连的 IP 与 dig 结果对比,是这类故障最快的定位手段
  • 长期运行的进程会缓存旧的 DNS 解析,服务端 IP 一变就静默失效
  • "进程还活着"不是证据:假活有三种形态,健康检查必须探端到端可用性
  • 根治顺序:服务端静态 IP → 配置用域名 → 节点健康检查让 fallback 自动切 → 定期重载

延伸资料