有人预先创建了 tun1,配置文件写的是 dev tun1,但连接仍然挂不上。主机上存在这个名字几乎说明不了什么,因为挂接取决于命名空间、设备类型、链路归谁所有,以及守护进程被允许做什么。复用设备还逼出一个默认配置所掩盖的决定:由哪个组件写入地址和路由。两种安排的故障方式差异远大于配置行的差异,所以要在改配置之前先做决定。

名字不等于挂接

dev tun1 向 OpenVPN 请求那个指定名称的接口,传统用户态构建会尝试打开它。如果名字不符合常见的 tun 或 tap 模式,配置还需要匹配的 dev-type,因为类型从不从字符串推断。TAP 设备不是换了标签的 TUN 设备:挂错设备要么直接失败,要么产生一条承载着没人预期帧的链路。先检查类型,再谈其他,因为后续每一项检查都以双方在这一点上达成一致为前提。然后确认进程真的能打开设备,而不只是能看到它。

dev tun1
dev-type tun

网络接口与 /dev/net/tun 是两个不同的对象:一个是命名空间内的链路,另一个是用户态程序用来挂接的字符设备。Linux TUN/TAP 文档描述了这条挂接路径以及持久设备的归属规则。能运行 ip link 的 root shell 只证明 shell 有权限,不证明守护进程有。设备节点的归属、服务的权限集合以及它运行所用的用户,三者必须对齐,打开才会成功。

存在却打不开的设备是归属问题,不是路由问题。排查时就按归属问题处理,否则一下午都会耗在读那些从来不是问题的路由表上。

谁来配置链路:两种归属模型

在第一种模型里,OpenVPN 在拉起连接的过程中配置地址和路由。这是默认行为,也最容易推理,因为单一组件创建状态又把它撤掉。在第二种模型里,外部管理器拥有接口,而 ifconfig-noexec 阻止 OpenVPN 执行它的地址配置命令,改为把参数交给脚本。于是管理器必须自己施加正确的本地端与对端配置,这比选项名字暗示的工作量更大。在两者之间做选择,问的是链路在运行时谁有权改动它。

ifconfig-noexec 只覆盖地址:路由表不动,路由配置属于 route-noexec。要把这两条指令当成两个归属不同的控制项,因为设了其中一个就假定另一个,是半配置链路的常见来源。每一层只选一个归属方,地址放在一处,路由放在一处。网络管理器与 VPN 助手同时写地址,正是断开连接时会抹掉对方仍依赖的状态的原因。

从真正重要的命名空间里检查设备

要从 VPN 进程所在的命名空间检查设备,而不是你 shell 恰好所在的那个。下面两条命令回答的是不同的问题,当宿主机能看到容器看不到的接口时,差别就出现了。请按给定顺序执行,因为只有先知道链路是否存在,第二条才有意义。

ip -details link show dev tun1
ip tuntap show

第一条命令按内核的视角打印链路,包括标志位和接口类型,这样你才能确认这就是想找的设备。第二条列出持久 tun/tap 条目,它们无需任何进程持有就能存在。等待既定归属方的接口,与连接到另一个 VPN 的活隧道是不同的情形,只有第二条才能解释你自己的挂接为什么失败。改动任何东西之前,先找到持有该设备的进程,因为持久条目与忙碌接口的修法并不相同。

多队列支持是一种有自身要求的特定驱动安排,并不是允许无关进程共用同一接口的许可。另一个归属方的活动会话是协调问题,改配置解决不了。

两种不同的持久性

内核持久性与 persist-tun 描述的是容易混淆、混淆起来代价又高的生命周期。持久设备独立于任何进程存在,因此能挺过停止,下一次启动可以复用。persist-tun 在单个 OpenVPN 进程内部跨受支持的软重连保留资源,视野短得多。两者都不保证停止服务后路由和地址仍然留在原处,而一个仍然可见的接口并不是加密会话在正常工作的证据。

也要确认后端,因为预先创建的接口并不说明哪个实现会打开它。带有直接数据路径的社区构建会尝试使用它,除非设计另有说明。在版本支持该开关的地方,disable-dco 转而选择用户态路径;更老的引擎和其他客户端没有这条指令,所以要把版本条件写在选项旁边,而不要假定它适用。

只测启动时会坏在哪里

归属约定可能通过第一项测试却在最后一项失败,这让单测启动成为很弱的检查。要观察两种切换:一次软重连,以及一次完整停止后的启动。

systemctl status openvpn-client@lab
ip -details link show dev tun1

启动是简单情形:单元起来,链路出现,状态输出给出已加载的配置名。挂接是守护进程打开设备的那一刻,所以要在单元报告处于活动状态之后再做链路检查,而不是之前。如果状态还在激活时链路就已存在,你看到的是一个尚未被任何人挂接的持久设备,而不是一条能用的隧道。继续之前先让真实流量走一遍链路,因为挂接了却没有可用路由的接口仍然算失败。

kill -SIGHUP $(pidof openvpn)
journalctl -u openvpn-client@lab -n 40
systemctl stop openvpn-client@lab
ip -details link show dev tun1

软重连应当让会话仍然可用、资源完好无损,这正是 persist-tun 做的事。完整停止才是外部管理器现身的地方:如果链路或其地址挺过了停止,那就是别人在拥有它们,这要么是你记录在案的状态,要么就是你刚发现的缺陷。把这两项观察记在配置旁边,好让下一次改动从证据出发,而不是从示意图出发。

相关文章