闯关预计 30 分钟
闯关:隧道昨天还好,今天不通了
在模拟终端里从「浏览器打不开」一路查到具体哪一跳断了。
学完这节你能做到
- 按客户端 → 本地监听 → 隧道 → 出口逐段定位
- 区分 DNS 失败、监听没起、隧道断开与出口被封
- 给出可验证的结论与恢复步骤
建议先学
跳过这几节会看不懂本节的部分推导
接手
自己的开发机,今天早上开始出问题:
浏览器打不开 GitHub,
git push卡住,pip install超时。昨天下班前还好的。 隧道进程看着是活的。
已知信息:
- 本地 Mihomo 监听
mixed-port 7892,出口是自建的 anytls 服务器proxy.example.com:443 - 昨晚没改过任何配置
- 公司内网服务访问正常
✓怎么玩
按 L5 的排障顺序:本地监听 → 隧道连接 → 出口可达 → 目标可达。
输入 goals 看目标,hint 要提示。四个目标全达成即过关。
"进程还活着"这句话不能当证据用 —— 想想上一节讲的 ExitOnForwardFailure。
- 1.确认本地代理端口是否还在监听
- 2.确认问题出在代理链路而不是本机 DNS 或系统代理设置
- 3.从日志确认代理到出口的连接失败在哪一步
- 4.找出根因:确认域名解析到的地址是否与实际使用的一致
浏览器打不开 GitHub、git push 卡住、pip 超时。 本地 Mihomo 在 7892,出口 proxy.example.com:443。昨晚未改配置。
[wutz@devbox ~]#
结论应该长什么样
| 证据 | 说明 |
|---|---|
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'
根治(按有效性排序):
- 服务端用弹性/静态 IP,别让它随重建变化
- 客户端配置里用域名而不是 IP(这次已经是域名,问题在缓存)
- 给节点配健康检查,让
fallback组能自动切走 - 定期重启或重载长期运行的代理进程
×「进程还活着」不能当证据
这一关和 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 对比 ← 本例的根因
⑥ 出口到目标通不通 在出口服务器上验证
每一步都只确认一件事,确认完就往下走一跳。 乱序排查(比如一上来就重装客户端)会让你丢掉现场,还找不到原因。
检查点
curl 报超时而不是 connection refused,这个区别说明什么?
代理日志显示 dial tcp 203.0.113.77:443: i/o timeout,而 dig 解析出的是 203.0.113.201。说明什么?
为什么健康检查不能只看进程是否存活?
这一关的落点
- 排障顺序固定:本地监听 → 本地代理响应 → 规则与节点 → 代理到出口 → 出口地址 → 出口到目标
- timeout 与 refused 方向相反:前者包没到,后者到了被拒
- 显式
curl -x <本地代理>能一步把"系统代理设置"这个变量摘掉 /connections的rule与chains两个字段可以排除掉最常见的自己作的问题- 把日志里实际在连的 IP 与
dig结果对比,是这类故障最快的定位手段 - 长期运行的进程会缓存旧的 DNS 解析,服务端 IP 一变就静默失效
- "进程还活着"不是证据:假活有三种形态,健康检查必须探端到端可用性
- 根治顺序:服务端静态 IP → 配置用域名 → 节点健康检查让 fallback 自动切 → 定期重载