先让 Tor 就绪再启 VPN:把启动门控排对序
运行中的 tor 进程不是承载方。用一个有文档记录的客户端就绪信号去门控 VPN,再门控它自己的对端,并把兜底路径分开。
盯着 tor 进程然后去启动 VPN,是把这套栈搭错最常见的方式。守护进程出现得远早于它能承载客户端流量,而固定等待则是伪装成依赖关系的猜测。门控必须是下一个组件真正需要的状态。请按顺序走完下面四步,并把每一步当作独立条件,而不是一个需要调的延时。
第 1 步:选择你实际拥有的传输方式
先定协议,因为其余设计都取决于它。Tor 的 SOCKS 接口不实现 UDP ASSOCIATE,所以经它承载的任何内容都必须是 TCP 流。使用 UDP 的 OpenVPN 配置无论监管程序怎么写,都无法走这条路径。把配置切到 TCP,并在改动启动逻辑之前确认远端对端接受它。
然后确认你的客户端究竟能不能到达 SOCKS 代理。OpenVPN 2.6 为 TCP 配置记录了 socks-proxy 选项,而不同客户端提供的代理支持并不相同。一个悄悄忽略代理选项的启动器会直接连接,看起来还能工作,这比失败更糟。
把选定的传输方式写进运行手册,因为后续每一项检查都依赖它。
第 2 步:读 bootstrap 阶段,而不是进程列表
Tor 控制协议对 GETINFO status/bootstrap-phase 的答复是最新的 bootstrap 状态,其中包括进度百分比和摘要标签。这个答复之所以有用,正是因为控制器可能在守护进程已经完成 bootstrap 之后才连上。只监听事件的监管程序会漏掉 Tor 在监管程序启动之前就已就绪的情况。
正确认证控制连接。控制端口是管理接口,任何能与它对话的人都能重新配置守护进程。把它留在 localhost 上,保持 cookie 或密码认证开启,不要为了让启动脚本更简单而删掉其中任何一个。
启动时读一次阶段,随后订阅进度事件以获得实时视图。
printf 'AUTHENTICATE\r\nGETINFO status/bootstrap-phase\r\nQUIT\r\n' | nc 127.0.0.1 9051
ss -ltn | grep -E '9050|9051'
tor --verify-config -f /etc/tor/torrc带进度 100 和 done 标签的答复意味着守护进程完成了自身启动。这还不意味着到你的目的地已经存在可用电路。
第 3 步:等待客户端电路事件
Tor 事件协议定义了 CIRCUIT_ESTABLISHED,它报告客户端有了可用的电路。这正是承载方门控想要的就绪信号,因为它比百分比更接近你真正需要的能力。订阅该事件并记录这次转变,让日志显示承载方何时变得可用,而不是进程何时出现。
把事件当作可以尝试的许可,而不是对某个特定目的地的保证。出口策略仍然可能拒绝远端对端,VPN 自身的握手也可能因为与 Tor 毫无关系的原因而失败。
在代码里把这两个条件分开。一个名为 ready 的布尔值会把它们合并,让下一次故障无从判断。
第 4 步:用 VPN 自己的握手来门控它
VPN 有自己的就绪条件,也只有它能证明隧道已经起来。等到客户端报告已连接状态并且分配到了地址,再检查代理要求是否始终有效。一个在失败一次之后就丢掉 socks-proxy 选项的重试循环会直接连接,并悄悄让整套设计失效。
在对端不可达时会发生什么,要在你需要知道之前就想清楚。带可见失败的有上限重试比无限循环更容易运维,也给监控系统留下了告警的着力点。同样的决定也适用于承载方:如果 Tor 重启,VPN 必须被拆掉并重新门控,而不是留在一条死掉的电路上继续跑。
最后,在 VPN 停摆期间保持应用自身的策略有效。一个正确等待的监管程序,并不能阻止无关进程直接发送流量。
检查你必须挺过去的四次转变
四种情况覆盖了这套设计:Tor 先起来的冷启动、监管程序接上已就绪的 Tor、VPN 对端不可达,以及隧道在工作时承载方丢失。每一种的预期转变都不同,只要门控写对了,每一种都会产生不同的日志行。
故意一次只跑一种,并且读状态变化,而不是读进程列表。
journalctl -u tor@default -n 30 --no-pager
systemctl status [email protected]
curl -sS --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip承载方掉线时,VPN 应该在一个重试间隔内随之落下,而不是挂着一条过期隧道。Tor 回来时,门控只应该在电路事件之后重新打开,而不是在进程启动之后。把每次失败时缺的是哪个条件记下来,下一个值班的人就不必从时间戳里重建顺序。