1实现原理 · 为什么它能做到
它把「Tailscale 与代理/VPN 共存」的冲突按网络栈层级拆成 5 层,每层标注「什么坏了 / 什么还好 / 根因」,用「谁还在工作」反推故障层——这是整个 skill 的诊断骨架。
| Layer | What breaks | What still works | Root cause | |-------|-------------|------------------|------------| | 1. Route table | Everything (SSH, curl, browser) | `tailscale ping` | `tun-excluded-routes` adds `en0` route overriding Tailscale utun |
它的核心方法论是「先验证组件自己的健康端点,别从旁证下结论」——因为多层可以产生重叠症状,先跳到最具体的验证可以避免钻进错误的一层。
When symptoms point at a component (proxy, VPN, route table, DNS), **don't commit to a hypothesis from circumstantial evidence — verify with that component's own health endpoint first.**
针对 TUN 全局模式下「探针会撒谎」这一现象,它给出可判定的证伪标准:0.00s 的跨洋连接、亚毫秒 ping 在物理上不可能,因此测到的是本地 TUN 而非网络。
**Counter-move**: before citing any latency / reachability number while a TUN is up, ask *"would this number be physically possible if the packet really traversed to the destination?"* A `0.00s` connect or a `0.2ms` ping to another continent is the tell that you measured the TUN, not the network.
对同一句错误信息给出两套互斥解释并要求先做区分实验:198.18.x.x 只证明有 TUN,不证明 fake-IP 导致了失败;(A) 协议误判与 (B) 转发路径间歇性抖动需要相反的修法。
**First, a warning about this symptom**: the `198.18.x.x` in the error message is **not evidence that fake-IP caused the failure**. Under TUN, *every* hostname resolves to a fake IP, so that address appears in the error whether the connection failed for this reason or any other.
它把「端口探测在 TUN 下全不可信」变成了一条必须先做的校准实验:探一个目标肯定不服务的端口,若也报开,则该机器上所有端口探测零信息量。
**First: stop trusting `nc -z`.** Under a TUN the local stack completes the TCP handshake on the destination's behalf, so `nc -z` succeeds for **every** port, including ports the destination has closed.
「隧道还是对端防火墙」这个在客户端无法区分的问题,被要求走带外通道(云厂商 guest-agent)进入目标机取证,并给出「journalctl 而非 auth.log」的实测反例。
**Then ask the destination out-of-band.** The only way to settle "did my packets arrive?" is a channel that does not traverse the failing data path. On a cloud VM that is the provider's guest-agent control plane
自带的快速诊断脚本把最常见的四类 macOS 冲突(env 代理、系统代理例外、直连/代理路径分裂、本地 TLS 信任)压成一条命令,并给出可判读的三行结论。
python3 scripts/quick_diagnose.py --host local.example.com --url https://local.example.com/health
对「破坏性网络变更」设了前置门:动 VPN/路由/服务之前必须先读 recovery 参考、先验证前置条件、先布好有界恢复手段,并明确「停成功/进程脱离/脚本末尾重启」都不算恢复证据。
Before any authorized VPN disconnect, app termination, route cutover, or service restart, read [network_change_recovery.md](references/network_change_recovery.md). Validate prerequisites before interruption, arm bounded recovery before the first disruptive action, and verify both the repaired path and the user's original network use. A successful stop command, a detached process, or a restart placed at the end of a script is not recovery evidence.
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| cli | tailscale(status / ssh / up / version) |
| cli | macOS 网络诊断工具族(ifconfig / netstat / route / scutil / lsof / dscacheutil) |
| cli | curl(含 -x 显式代理、--noproxy '*'、-w 计时模板) |
| cli | dig / nslookup(逐 nameserver 二分 DNS) |
| cli | nc / tcpdump / ss(端口探测与到达性取证,含 sudo tcpdump) |
| cli | ssh / ssh -vvv / ssh -T(连通性与 git over SSH 诊断) |
| cli | docker / docker build / docker run(VM 与容器代理传播诊断) |
| cli | pnpm(SOP 示例,含 npmmirror 镜像参数) |
| cli | 阿里云 CLI(aliyun ecs …,带外命令执行取证) |
| cli | journalctl / grep(目标机日志取证;经带外通道执行) |
| api | Shadowrocket 配置编辑器 API(读/写整份代理配置,LAN 内 HTTP) |
| network | 通用可达性目标(Google generate_204、api.github.com、alpine CDN)用于路径健康与容量测量 |
| network | Tailscale 官方站点(ACL 管理页与独立版安装包) |
| network | 本地/内网服务与 vanity 域名(文档中的占位目标,非固定端点) |
4风险提醒 风险提醒:橙色 · 评估后使用
- 会在用户的网络栈上做真实变更,误判可导致断网 — 改路由、改 skip-proxy/tun-excluded-routes、重启 Tailscale、WSL 侧 sudo snap remove/服务开关都在其能力范围内。文档要求先读 recovery 参考并布置有界恢复,但这是流程约束;在远程/无人值守机器上执行有失联风险。
- 代理配置的读取与写回是高风险动作 — Shadowrocket 的 LAN HTTP 配置 API 可读整份配置(含节点凭据),写回时若用 -d 而非 --data-binary 会因 URL 编码**彻底破坏**全部规则与代理组(文档原话)。且 8080 端口可能属于别的服务,误发请求可能打到无关服务上。
- 跨机操作的授权边界靠人工判断 — skill 会 SSH 到 Tailscale 主机、并用云厂商 RunCommand 在目标机执行 shell。文档划了 scope 门并强调『保存过的 SSH 访问不等于长期使用许可』,但实际是否越界取决于当次执行者的判断。
- 一处安全降级表述:Step 6 验证命令关闭主机密钥校验 — `ssh -o StrictHostKeyChecking=no` 写进了推荐的端到端验证命令,而 Step 2H 又反对为省事放宽该检查。影响有限(命令只跑 echo/hostname/whoami),但使用者可能照抄到其他场景。建议去掉该选项或改用 known_hosts 预置。
- 代理节点凭据可能进入会话记录 — 读取整份代理配置后,内容会作为工具输出进入 agent 上下文与日志;若被整段回显,节点地址与凭据会留在会话/转录里。建议只提取需要的规则片段。
- 门槛偏高且强依赖完整阅读 — SKILL.md 80KB / 1290 行 + 5 份 reference,含大量前序版本修正与边界条件;只读片段容易漏掉「先校准 nc」「先查故障线」「先读 recovery」这类关键前置,从而复现文档记录过的误判。
5第二遍独立确认
- [discrepancy] 每条外部依赖的调用点是否真实存在(13 个 CLI/API/network 条目) — 【本项在第二遍自查时曾误判为 ok,后经磁盘级校验工具抓出并已修正】。逐条回查后确认:tailscale 调用点见 Step 2D 的 `tailscale status` 与诊断纪律表的 `| Tailscale daemon | \`tailscale status\` returns peer list (not connection error) |`、Step 5 的 `sudo tailscale up --ssh` / `--ssh=false` / `tailscale ssh` / `tailscale version`;其余依赖亦各有真实调用点(env/ifconfig/route/scutil 见 2A/2B、curl 见 2A/2C/2E 与容量参考、dig 见 2I/2J/2K、nc/tcpdump/ss 见 2K 与 Step 6、ssh 见 Step 6 与 SSH 双隧道、docker 见 2G、pnpm 见 SOP 第 5 节、aliyun ecs 与 journalctl 见 2K、Shadowrocket config API 见 proxy_conflict_reference.md、google/github/alpine CDN 见 2H/2K/2G、Tailscale 官方站点见 Step 4/5B、localhost/local.<domain> 见 Step 1 症状清单)。**纠正的具体错误**:首稿有两条 evidence 的 quote 被误取自同批另一份源码(windows-remote-desktop-connection-doctor/SKILL.md)——`tailscale status` / `tailscale netcheck` 与 `ifconfig | grep -E "^[a-z]|inet |utun"` / `netstat -rn | head -40` / `scutil --proxy` 均属该文件而非本 skill;本 skill 内 tailscale 的实际命令是 `tailscale status`、`tailscale ssh`、`sudo tailscale up --ssh`、`tailscale version`(**不含 netcheck**),macOS 工具的实际命令是 `ifconfig | grep -A2 'inet 100\.'`。两条 quote 已按 pin 源码逐字替换,`netcheck` 相关表述已从 name/scripts_executed 中清除。教训记录:跨文件批量取证时,同批并行读取的相邻源码极易被错误归属,evidence 必须在写入前逐条回到目标文件复核。
- [ok] 自带脚本是否与文档声明一致(是否只做诊断、不写盘、无 shell 注入) — quick_diagnose.py 的 docstring 自述只做五类冲突检测(env+NO_PROXY、系统代理例外、代理路径 vs 直连、本地 TLS 信任、可选路由归属);其 subprocess.run 全部传参数列表(无 shell=True);os.environ.copy() 后仅 pop 代理变量以做对照请求;无文件写入、无 subprocess 之外的网络栈。结论成立。
- [ok] 是否漏记外发目标(把每个域名回查调用点) — 全目录域名提取:api.github.com、www.google.com、dl-cdn.alpinelinux.org、registry.npmmirror.com、login.tailscale.com、pkgs.tailscale.com、docs.python.org、docs.ansible.com、tailscale.com、local.example.com、localhost(多端口)、127.0.0.1:1082、host.internal:1082、192.168.31.110:8080(Shadowrocket IP 占位)、100.101.102.103:8002、203.0.113.199(RFC 5737 对照)、198.18.x.x(fake-IP 段)。均已归入 external_deps 或注入面说明;无未记载的隐蔽外发。
- [discrepancy] 『安全降级』检索是否有命中 — 命中并构成文档内部张力:Step 6 的端到端验证命令为 `ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no <user>@<tailscale-ip> 'echo SSH_OK && hostname && whoami'`,即关闭主机密钥校验;而 Step 2H 在讨论『不要硬编码 GitHub IP』时明确把『pressure you into loosening `StrictHostKeyChecking`』列为真实成本,并称「That is a real cost paid for an uncertain benefit.」。同一文档一处反对放宽该检查、另一处把它写进推荐验证命令,属于未消解的表述张力(实际影响有限:该命令只执行 echo/hostname/whoami,无数据外发)。按分级范式这不足以把本 skill 判红,但应作为使用提醒记录下来。
- [ok] 『读取代理配置』这一凭据接触面是否被文档自己披露 — 披露且被谨慎处理:proxy_conflict_reference.md 明确要求不要硬编码设备 IP('The device IP changes with DHCP. Do not hardcode it.')、给出端口 8080 可能被别的服务占用的 lsof 检查、并用粗体警告 -d 会 URL 编码从而『destroys the entire configuration』。即作者意识到了该接口的高风险性。此事以「凭据读取 + 配置写入」双重性质记入 security,不构成隐蔽行为。
- [ok] 是否存在混淆或隐蔽脚本 — 全目录扫描 >200 字符无空格长行零命中;无 base64 大块(仅 2K 节的 aliyun --ContentEncoding Base64 说明与 RFC 引述);唯一可执行文件 quick_diagnose.py 全文可读、无动态代码加载。
- [ok] 文档内部是否另有自相矛盾(除 StrictHostKeyChecking 外) — 抽查了 nc -z 的表述:Step 1 的关键区分里曾以 nc -z 成功作为判据,Step 2K 则明确『This supersedes the plain `nc -z` reading in the bullet above whenever a TUN — not just Tailscale — is in the path』——即作者主动标注了覆盖关系而非留矛盾,属已消解的张力。其余抽查(utun MTU 判据、TUN 可信探针清单)内部一致。
- [ok] pin commit 与 skill.path 是否与任务书表格一致 — git rev-parse HEAD = d5c4678cb5d4fd6acc9c922690df035dbd33d247,与任务书表格『库内 HEAD』一致;目录位于仓库根,相对路径 tunnel-doctor;本目录最后一次提交 829d9758(2026-09-08 22:22)。GitHub API 复核 MIT / stars 1392 / pushed 2026-09-15T09:30:04Z。
6结论
a9f26e4f66655f0e…d5c4678cb5