OpenVPN 在 TLS 错误后反复重试:如何让它停下来
TLS 握手失败默认会被重试,于是客户端周而复始,第一条错误被刷出视野。tls-exit 会让它退出;先查清是什么在把它拉起。
客户端连上、在 TLS 协商阶段失败、等待,然后又去试同一台服务器,结果别无二致。若由会重启失败进程的守护机制看管,这些尝试就变成日志里永无止境的循环,第一条诊断信息被刷得无影无踪。OpenVPN 提供了一个选项,能把这次协商失败变成一次看得见的退出,而不是又一次重试。它属于诊断与看护手段,不是修复手段,所以要配上一条真正允许进程停下来的策略。
藏起第一条错误的循环
你看到的不是一次失败,而是一个重复出现的段落:连接尝试、TLS 错误、停顿,然后是同样的一串。每重复一次,最初那行就被推得更高。如果该 unit 在失败时自动重启,这个循环可以持续数小时而无人去读原因。症状是循环,可底层的毛病只是同一个协商问题。
交互式会话能容忍它,因为暂时性的失败也许下一次就过去了。自动化连接却承担不起这份乐观:进程必须停下来,人或监控才能看到状态。
动手之前,先完整记录一轮循环。记下远端地址、错误中提到的 TLS 阶段,以及进程究竟有没有退出。
循环背后的重试机制
默认会重试,是因为几个互不相干的超时共用同一套机制。connect-retry 设定两次连接尝试之间的间隔,connect-retry-max 可以限制每个远端条目被尝试的次数,resolv-retry 则管着反复的名字解析。TLS 协商失败是作为传输层事件进入这套机制的,所以客户端不报告,而是安排下一次尝试。这个默认值对不稳定的链路有用,对无法自愈的故障却有害。
--tls-exit 选项只改变协商这一种情形。手册把它描述为 TLS 协商失败时退出,这比任何错误都退出要窄。超时、解析失败与认证被拒仍保持各自的行为。
让客户端在 TLS 失败时退出
把 tls-exit 放进客户端真正加载的那份配置,或者在启动时传入 --tls-exit,然后在前台跑一次。既要读退出状态,也要读日志最后一行,因为守护机制依据的是状态。协商失败后以非零码退出,才是预期的结果。如果进程仍在重试而不结束,说明该选项并不在客户端真正加载的配置里。
这次退出是否看得见,由守护机制决定。带 Restart=always 或 Restart=on-failure 的 systemd unit 会把客户端再拉起来,循环于是继续,选项看起来毫无作用。责怪客户端之前,先检查策略:
systemctl show -p Restart -p RestartSec openvpn-client@office
journalctl -u openvpn-client@office -n 40 --no-pager
Restart=no 让进程留在原地停着,而 Restart=on-failure 在非零状态之后依旧会重启。若希望故障可见、但服务又能从不相关的崩溃中恢复,可以设 Restart=on-failure,并用 StartLimitBurst 和 StartLimitIntervalSec 止住重启风暴。随后读 unit 状态确认最后的退出码:systemctl status 会报告它,日志则把第一条错误留在一次次重启之上。
把决定写进 unit 或配置文件里的注释。若原因没有记录,一个 TLS 失败就退出的配置在下一个人看来就像一场事故。写清哪些失败应当让进程停下、哪些应当重试。
用一个一次性端点来验证
在信任这一行为之前,先复制配置,把副本指向一个能接受 TCP 连接、却从不开口说 OpenVPN 的端点。一个会应答、随后保持沉默的监听器就够了。加上 tls-exit,握手失败时客户端应当结束;去掉它,同一份配置应当继续打转。用副本,绝不要用正在工作的那份。
比较两次运行,要看退出状态和第一条错误,而不是各自持续了多久。状态说明选项有没有生效,第一条错误则说明卡在了哪一阶段。
用完之后把测试配置删掉。一份故意把端点写错的副本,对之后启动目录里每个配置的人来说就是个陷阱。
这个选项永远到不了的阶段
认证被拒属于更靠后的阶段。如果服务器核对账号后拒绝,TLS 早已协商完成,tls-exit 便不会触发。下一步该看认证的重试设置,而服务器的原因比客户端最后一行更重要。密码错误与证书缺失,在客户端一侧可能给出相似的信息。
路由缺失、解析器配错、网关不可达,同样不在这个选项的管辖范围内,因为它们都不是协商失败。静默丢包是最不显眼的一种:视交换进行到哪一步,客户端可能报告协商失败,也可能只是超时。
把这种区分留在笔记里。该设置是为了诊断与看护,而不是修复,所以它治不了 加密套件不匹配、证书问题或端口写错。先修好协商失败的原因,再判断立即退出是否仍适合这次部署。