工具大全
踩坑实录作者:Coocon2026年9月17日31 次阅读约 5 分钟阅读

服务、端口、证书全正常,VPN 却断了 4 小时:Tailscale 接管 DNS 后把翻墙机的解析清空了

现象

两台同机房、同配置的 DMIT 洛杉矶 VPS,都跑 sing-box 做翻墙节点(VLESS-REALITY 走 tcp/443,Hysteria2 走 udp/443)。9 月 16 日晚上,主力那台(下文叫 dmit-usa)的两个节点在客户端全部超时;另一台(dmit-usa-eb)一切正常。

登上去第一轮检查,什么都是绿的:

$ ssh dmit-usa 'systemctl is-active sing-box nginx; ss -lntup | grep -E ":443 |:8444|:8445"'
active
active
udp   UNCONN 0 0   *:443            *:*   users:(("sing-box",...))
tcp   LISTEN 0 4096 127.0.0.1:8444  ...   users:(("sing-box",...))
tcp   LISTEN 0 511  127.0.0.1:8445  ...   users:(("nginx",...))
tcp   LISTEN 0 511  0.0.0.0:443     ...   users:(("nginx",...))

nginx -t 通过,Hysteria2 用的 Let's Encrypt 证书还有 81 天,systemd 里 sing-box 最近一次重启是两天前的证书整治,之后没有任何异常退出记录。

唯一不对劲的是日志目录:

-rw-r--r-- 1 sing-box sing-box 13710190 Sep 16 23:15 sing-box.log      # 今天,13 MB
-rw-r--r-- 1 sing-box sing-box  7329135 Sep 16 00:30 sing-box.log.1    # 昨天,7 MB
-rw-r--r-- 1 sing-box sing-box    50237 Sep  7 00:15 sing-box.log.10.gz # 平时压缩后几十 KB

平时一天几十 KB,这两天一天十几 MB。服务没死,但它在狂刷什么东西。

定位:三条命令从"日志变大"走到根因

第一步:日志里刷的是什么

把最近 2 MB 日志的错误行归一化后计数:

tail -c 2000000 /worker/logs/sing-box/sing-box.log \
  | grep -oE "(ERROR|failed|handshake)[^\"]{0,80}" \
  | sed -E 's/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(:[0-9]+)?//g' \
  | sort | uniq -c | sort -rn | head
5307 ERROR  inbound/vless[vless-in]: process connection from
2826 handshake: REALITY: failed to dial dest: lookup www.apple.com: (exchange6: SERVFAIL
2499 handshake: REALITY: failed to dial dest: lookup www.apple.com: (exchange4: SERVFAIL
  40 ERROR  connection: open connection to oauthaccountmanager.googleapis.com:443 ...

两条信息:

  • REALITY 握手失败,原因是 lookup www.apple.com 返回 SERVFAIL。REALITY 协议的机制是服务端在握手阶段真的去连一次借用的目标站(这里是 www.apple.com),借它的 TLS 握手来伪装;目标站解析不了,握手就进行不下去,每一个 VLESS 连接都会被拒。
  • 后面那些 open connection to xxx:443 失败,是 Hysteria2 已经建好隧道之后,出站去连目标域名时同样解析失败。所以两种协议一起断,而且断法一样:客户端能连上端口,但一个网页都打不开。

第二步:是谁在回答 DNS

$ cat /etc/resolv.conf
# resolv.conf(5) file generated by tailscale
# For more info, see https://tailscale.com/s/resolvconf-overwrite
# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN

nameserver 100.100.100.100
nameserver fd7a:115c:a1e0::53
search tailaca6af.ts.net
$ dig +short www.apple.com          # 走系统 DNS → 100.100.100.100
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL
$ dig +short @1.1.1.1 www.apple.com # 直接问公网
www-apple-com.v.aaplimg.com.

系统 DNS 是 Tailscale 的 MagicDNS 存根 100.100.100.100,它回 SERVFAIL;绕过它直接问 1.1.1.1 一切正常。网络没坏,坏的是这台机器上负责转发 DNS 的 tailscaled。

第三步:tailscaled 为什么不转发了

$ journalctl -u tailscaled --since today | grep -i dns | grep -v RATELIMIT | tail
Sep 16 09:52:30 tailscaled: trample: resolv.conf changed from what we expected. did some other program interfere? current contents: "nameserver 1.1.1.1\nnameserver 1.0.0.1\n"
Sep 16 09:52:30 tailscaled: dns: Resolvercfg: {Routes:{.:[1.1.1.1 1.0.0.1] ts.net.:[199.247.155.53 ...]} ...}
Sep 16 18:58:20 tailscaled: trample: resolv.conf changed from what we expected. did some other program interfere? current contents: ""
Sep 16 18:58:20 tailscaled: dns: Resolvercfg: {Routes:{.:[] ts.net.:[199.247.155.53 ...]} ...}
Sep 16 23:16:21 tailscaled: dns: resolver: forward: no upstream resolvers set, returning SERVFAIL

三行日志把整件事说完了:

时间 resolv.conf 被谁改成了什么 tailscaled 重新接管后的公网上游(Routes:{.:[...]} 结果
09:52:30 外部程序写成 1.1.1.1 / 1.0.0.1 [1.1.1.1 1.0.0.1] 正常
18:58:20 外部程序写成空文件 [] 此后所有公网查询 no upstream resolvers set, returning SERVFAIL

而 09:52:30 和 18:58:20 这两个时刻,系统日志里都恰好有同一件事:

Sep 16 18:58:20 dhclient[533]: DHCPREQUEST for 179.253.232.64 on eth0 to 193.41.250.250 port 67
Sep 16 18:58:20 dhclient[533]: DHCPACK of 179.253.232.64 from 193.41.250.250
Sep 16 18:58:20 dhclient[533]: bound to 179.253.232.64 -- renewal in 36300 seconds.

eth0 是 DHCP 配置的,dhclient 每次续租(约 10 小时一次)都会重写 /etc/resolv.conf。第一次它写进去的是完整内容,tailscaled 重新接管时把这两个地址当成"系统原有上游"记下来,没事;第二次 tailscaled 读到的是空文件(最可能是撞上了 dhclient 先清空再写入的窗口),上游列表变空,从此对所有非 ts.net 的查询直接回 SERVFAIL。sing-box 日志里 3.4 万条 SERVFAIL 全部在 18:58 之后,一条不差。

为什么另一台没事

dmit-usa-eb 装 Tailscale 更早(9 月 5 日),配置一模一样:accept-dns 开着、resolv.conf 归 tailscaled 管、tailnet 后台没配全局 nameserver。它只是运气好,dhclient 续租时还没被 tailscaled 读到空文件。同一颗雷,只是还没踩。 这也是"对照机正常"这条线索最容易误导人的地方:两台配置一样、一台好一台坏,直觉会往"坏的那台被单独动过什么"想,实际是两台都处在危险状态。

根因一句话

Tailscale 在 Linux 上默认 --accept-dns=true,会把 /etc/resolv.conf 指向自己的存根 100.100.100.100;它对公网域名的转发能力完全取决于它接管那一刻从 resolv.conf 里读到的上游。tailnet 后台如果没配全局 nameserver,这个上游就是唯一来源;任何会重写 resolv.conf 的程序(dhclient、systemd-resolved、NetworkManager、cloud-init)都可能让它读到不完整的内容,之后整机 DNS 静默失效。Tailscale 自己的文档把这类情况叫 "DNS fight"(日志里给的链接就是 tailscale.com/s/dns-fight)。

而 sing-box 的 REALITY 入站没有独立 DNS 配置,完全依赖系统解析。于是一条看似和翻墙毫无关系的改动(装个内网穿透工具)把 VPN 打断了。

修法:三选一,推荐第一种

方案 A(治本,两台机器一次搞定):Tailscale 后台 → DNS → Global nameservers 加 1.1.1.1 / 1.0.0.1。配置下发后,tailscaled 的公网上游来自控制平面,不再依赖 resolv.conf 里读到什么。本次就是这样修的,后台配完不用重启任何服务,两台机器 dig www.apple.com 随即恢复 NOERROR,sing-box 日志停止报错。验证命令:

tailscale dns status | sed -n '/Resolvers/,/Split DNS/p'
# 应看到 1.1.1.1 / 1.0.0.1 列在 Resolvers 下

方案 B(单机止血,不需要后台权限):让 Tailscale 不再碰 DNS,然后自己把 resolv.conf 写回去。

tailscale set --accept-dns=false
printf 'nameserver 1.1.1.1\nnameserver 1.0.0.1\n' > /etc/resolv.conf

代价是这台机器解析不了 *.ts.net 的 MagicDNS 名字,用 100.x 地址互访不受影响。翻墙机通常根本用不到 MagicDNS,这个代价可以忽略。

方案 C(让 sing-box 与系统 DNS 解耦):在 sing-box 配置里加独立的 dns 段,指定 1.1.1.1 之类的服务器。这样系统 DNS 再坏,REALITY 握手和出站解析也不受影响。它不修 Tailscale 的问题,但把翻墙服务的爆炸半径缩小了,适合作为 A 或 B 的补充。

复盘:为什么这个故障花了比预期长得多的时间

排查的实际顺序是:服务状态 → 端口 → 证书 → 两天前那次证书整治的 deploy-hook 有没有把 sing-box 重启坏 → 日志。前四步全部正常,而且"两天前动过证书"这条线索非常像根因,差点把时间全花在那上面。

真正的教训是三条:

  1. "服务正常"不等于"服务能用"。 systemctl is-active 只说明进程活着。对代理类服务,应该验收的是它的核心依赖:DNS 能不能解、出站能不能连。
  2. 日志体积突变是最早、最便宜的信号。 每天几十 KB 变十几 MB,比任何监控都先告诉你"它在狂刷错误"。看到这个直接进日志,不要再绕别的。
  3. 最近一次"无关"改动最可疑。 装 Tailscale 和 VPN 看起来毫无关系,但它改了 resolv.conf。在服务器上任何会动网络栈的操作,都要当成对所有依赖网络的服务的改动。

让 Claude Code 以后不再踩这个坑:该写进 CLAUDE.md 的规则

这次的 Tailscale 是让 Claude Code 装的,故障也是 Claude Code 查出来的。它查得对,但装的时候没有任何机制拦住它。事后我们把下面几条写进了 vps 仓库的 CLAUDE.md 和 tasks/lessons.md,思路是把"装完验收什么"写死,而不是指望模型每次都想到

1. 网络组件的安装验收清单

在翻墙机(跑 sing-box 的机器)上安装任何会接管 /etc/resolv.conf 的组件
(Tailscale / systemd-resolved / NetworkManager / cloud-init),装完必须验收:
- dig www.apple.com 走系统 DNS 返回 NOERROR
- 装前装后各记一次 sing-box 日志 ERROR 行数,增量为 0
- cat /etc/resolv.conf 确认托管方,托管方是 tailscaled 时后台必须已配全局 nameserver
验收不过不算装完。

2. 把隐性依赖写成显性事实

sing-box 配置无独立 dns 段,REALITY 握手要先解析 www.apple.com,
服务器系统 DNS 一坏 VPN 全断(服务/端口/证书全正常,systemctl 看不出来)。

模型不会自动知道"REALITY 握手依赖 DNS"这种项目级事实。写一句,它排查时就会把 DNS 排进前三项。

3. 排查顺序也可以写成规则

排查 VPN 不可用:① systemctl / 端口 ② dig www.apple.com ③ sing-box 日志体积与错误归一化计数
④ 最近 48h 的任何网络相关改动(包括看似无关的)。日志体积异常增大时直接跳到 ③。

4. 让它记住"对照机正常"不是排除依据

两台同配置机器一好一坏时,先确认好的那台是否只是"还没触发",不要默认它安全。

这四条都不是通用最佳实践,而是这台机器、这个架构下的具体事实和具体动作。CLAUDE.md 里最值钱的就是这类东西:模型靠通用知识推不出来、错了代价又很高的项目私有约束。

相关文章

相关文章

CLAUDE.md 不是 README:给 Claude Code 写规则的 5 条硬规矩

很多人把 README 或架构文档整个贴进 CLAUDE.md,结果 Claude Code 该守的规矩不守、不该改的文件乱改。原因是 CLAUDE.md 不是文档,是每一轮对话都常驻上下文的指令。本文给 5 条硬规矩:只写代码里推不出来的东西、越短越管用、硬规则交给 hooks 不靠文字、按层级拆文件、被纠正后当场回写。附一份可直接抄的骨架。

claude-code提示词+4
developer2026年9月12日4 min
139

Claude Code 报错 temporarily unavailable, so auto mode cannot determine the safety of bash 怎么解决

Claude Code auto 模式(自动批准权限模式)弹出「temporarily unavailable, so auto mode cannot determine the safety of bash」?先说结论:不是你的命令危险,是安全判定器(一次额外的模型调用)暂时联不上。本文给出四步处理、模型名×工具名×原因的完整变体速查、Shift+Tab 六种权限模式速查(plan / manual / acceptEdits / dontAsk / auto / bypassPermissions,实测自 v2.1.263),以及「auto 是什么模型」「bash denied by auto mode 是不是同一个问题」这些常见困惑的答案。

llmclaude-code+5
pitfalls2026年9月4日7 min
580

复现一条能打穿 Claude Code auto 模式的注入链:模型拒跑恶意二进制,却自写代码把自己坑了

embracethered 8 月底放出一条攻击链,让一句『总结这个网页』把 auto 模式的 Claude Code 拖到 60~80% 的代码执行成功率——而 Anthropic 委托第三方测出的数字是 0.00%。我在隔离环境里把这条链拆开逐段实测:诱导模型从 WebFetch 降级到 curl 的分流端点、以及最关键的一环——模型『拒绝运行陌生二进制、改自己写 Python 解码器』这个安全决定本身,反而踩中了同目录下的同名 struct.py 投毒。确定性部分(分流 + 同名模块投毒 + 缓解对照)在本机完整复现并给出真实证据;live 端我这台机器因判定器限流 fail-closed 而没能跑通完整 RCE,如实标注。文末给出真正有用的缓解手段。

claude-codeauto-模式+5
hands-on2026年8月31日9 min
427

抓包拆开 Claude Code auto 模式的判定器:11 万字系统提示词逐段解析

上一篇复测确认了 auto 模式在放行 Bash 前会调一次会话模型当判定器,但那个判定器收到的到底是什么,一直是黑盒。这次我用本地日志代理把判定请求整包抓了下来:一份 116,879 字符的系统提示词,开头写着 You are a security monitor for autonomous AI coding agents。本文逐段引用抓包原文,拆开它的威胁模型、两级规则(1 条 HARD BLOCK / 68 条 SOFT BLOCK / 17 条 ALLOW)和两阶段判定流程——第一阶段只评估危害、明确不看用户意图,第二阶段才叠加意图和豁免。附三张真实终端截图和抓包证据,所有数字均来自本次读出,未经估计。

claude-code提示词+5
hands-on2026年8月30日11 min
427