动手改配置文件之前,先数一数 OpenVPN 进程。一台既接纳漫游用户、又拨号连接上游服务的 Pi 会运行两个守护进程,各自读取由你输入的实例名所选择的配置文件。随后,打包好的模板会为每个守护进程分配独立的工作目录、独立的运行时状态文件和独立的隧道设备。这个划分一旦弄错,你就会花一晚上去修改某个运行中的进程根本不会打开的配置文件里的正确设置。

每个实例名挑选自己的配置文件

在随 OpenVPN 2.6 提供的单元文件里,启动参数带有一个实例占位符,由 @ 符号后面的文本填充。服务器模板在服务器配置目录中运行,并把参数解析为该目录下的 %i.conf;客户端模板则从客户端目录做同样的事。因此 [email protected] 读取服务器目录中的 admits.conf,而 [email protected] 读取客户端目录中的 uplink.conf,这两个名字在对方的目录里都毫无意义。

请在机器上确认这层映射,而不要假设软件包保留了上游默认值。发行版提供的 drop-in 完全可以替换工作目录或配置参数。

systemctl cat [email protected]
systemctl cat [email protected]

读取那份输出里的 WorkingDirectory 行和配置参数,因为决定加载哪个文件的只有被启用的那个单元。即使模板路径看起来正确,一个把 --config 追加到 ExecStart 的 drop-in 也会胜出。想知道是否存在本地覆盖时,请索取 fragment 与 drop-in 的路径。如果两个角色都解析到同一个文件,你就有单个进程既想当服务器又想当客户端,而该文件中靠后的选项会悄悄胜出。

本地传输与被转发的载荷命中不同的钩子

服务器发往每个漫游用户的加密连接在 Pi 本机生成,所以它的外层回复走本地输出钩子。客户端连向上游对端的连接同样是本地生成的输出,尽管它通往远端网络。被转发的载荷属于第三类,内核只有在已经为数据包选好出口设备之后才会匹配它。

FlowHook that sees itWhat to inspect
Roaming user handshakes to the serverLocal input and local outputListener socket, bind address, rule admitting the UDP port
User payload leaving through the upstream tunnelForwardIPv4 forwarding switch, selected route, permission rule, any translation
Pi's own connection to the upstream peerLocal outputUnderlay route to the provider endpoint, not the tunnel route

forward 链中匹配出口接口的规则,对一个路由已经指派给该接口的数据包可以放行或丢弃,但它从不选择接口。当一台主机终结一条隧道并又发起另一条时,把用户载荷送入上游隧道的策略与保护 Pi 自身加密回复的策略是两个独立决定。它们恰好落在同一块物理网卡上,所以常被混为一谈。

这一区分解释了常见的半截故障。用户能连上,握手也完成,流量却仍然走错方向,因为握手是本地流量、载荷是转发流量,而两者只测过其中之一。

导入的路由可能吞掉发往用户的回复

客户端配置文件的安全设置决定上游可以往 Pi 的路由表里推送什么,而范围过宽的 redirect 正是把一台正常工作的双角色主机变成坏掉的那台机的选项。如果被推送的路由覆盖了漫游用户的公网地址,服务器发往他们的回复就可能跟着新路由进入上游隧道,而不是从底层链路出去。人们常常确认到服务商的隧道仍然通畅就停下,因为那部分流量是隧道自身的传输,在其他一切都出错时它照旧流动。

基于源地址或 mark 的策略是让两条流保持分离的办法。OpenVPN 的 Linux mark 选项给守护进程自己的加密数据包打上防火墙 mark,路由规则随后可以把它送进专用表。当服务器的外层回复总是从同一个底层地址发出时,源地址方案同样有效。两种做法都需要所选表中存在有效路由,并为服务商的 endpoint 保留例外,否则隧道本身就起不来。

在写下任何规则之前,先向内核询问一条回复实际会走哪条路。下面这些地址是 Pi 底层地址与某个漫游用户公网地址的占位符。

ip rule show
ip route get 198.51.100.7 from 192.0.2.10

验证拆分,然后一次停一个角色

三条流要分别测试,因为握手成功并不能证明另外两条。接受一个新的入站连接,让一个被允许的应用经上游出口发出,并确认两个进程仍能各自到达对端。

然后停掉客户端角色,观察服务器角色还保留着什么。这是找出「本为某个实例准备、却被同时套到两者身上」的路由或 mark 规则最便宜的办法。

systemctl stop [email protected]
systemctl status [email protected] --no-pager
ss -lunp | grep openvpn
journalctl -u [email protected] -n 20 --no-pager

你想看到的是:客户端角色停掉时,监听套接字仍然绑定,隧道设备仍然存在,日志里没有新错误。如果停掉客户端同时也切断了服务器对既有用户的回复,那么两个角色共享了本该只属于其中一个的东西。记录每次改动触及的是哪个实例、哪个配置文件、哪条流的归属者,否则下一个调试这台主机的人会从零再发现一遍。

相关文章