1仕組みと動作原理
「Tailscaleとプロキシ/VPNの共存」による衝突をネットワークスタックの5つのレイヤーに分解し、各層に「何が壊れているか / 何が正常か / 根本原因」を明記した上で、「何がまだ動いているか」から障害層を逆引きする手法を採用しており、これがスキル全体の診断骨格となっています。
| 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.00秒の海外接続や1ミリ秒未満の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.
同一のエラーメッセージに対して2つの背反する解釈を提示し、事前の切り分け実験を義務付けています。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)経由で対象マシンに入って証拠収集するよう要求し、「auth.logではなくjournalctl」という実測の反例を提示しています。
**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で最も一般的な4つの衝突パターン(環境変数プロキシ、システムプロキシ例外、直連/プロキシ経路の分裂、ローカルTLS信頼)を1つのコマンドに集約し、判読しやすい3行の結論を出力します。
python3 scripts/quick_diagnose.py --host local.example.com --url https://local.example.com/health
「破壊的なネットワーク変更」に対して前提条件の関門を設けています。VPN/ルーティング/サービスを変更する前に、必ずリカバリ参照を読み、前提条件を検証し、制限付きのリカバリ手段を配備することを要求し、「停止成功/プロセス分離/スクリプト末尾の再起動」は復旧の証拠とみなさないことを明記しています。
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 …](リポジトリ同梱)
- 网络端点: 外部ネットワークアクセスの宣言: ローカル/LANプロキシポート(127.0.0.1:1082、host.internal:1082など)経由および直連による外部エンドポイントへのcurl実行(パスの健全性およびキャパシティ測定)
- 本地工作目录: ファイル読み書きおよび成果物出力: Shadowrocket設定の書き戻し:POST /api/save --data-binary @config.txt によるエディタバッファの置換(リファレンス)
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…