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外部依赖
| Kind | Dependency / Endpoint |
|---|---|
| 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管控建议与判定依据
- scripts/: 执行本地脚本或命令: python3 scripts/quick_diagnose.py --host … --url … [--tailscale-ip …](仓库自带,
- 网络端点: 声明外部网络访问: curl 经本地/局域网代理端口(127.0.0.1:1082、host.internal:1082 等)与直连访问外部站点做路径健康与容量测量(ap
- 本地工作目录: 文件读写与工件输出: Shadowrocket 配置写回:POST /api/save --data-binary @config.txt 替换编辑器缓冲区(referen
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…