从一台主机同时提供 TCP 与 UDP 的 OpenVPN 服务
2.7 之前,一个进程只绑定一种传输。比较桥接的 TAP 对、两个 TUN 子网和共享地址池,再把每个配置文件对应到它的监听器。
只允许 TCP 443 的网络与畅通 UDP 的网络共存,而一台服务器必须同时服务两者。带有 proto udp 的配置文件能回应第二类网络,却在第一类网络中失败;把同一份配置改成 proto tcp 也不会产生第二个监听器。该指令为一个进程只选一种传输。其余一切都是由这个限制推导出来的。
要么运行多个进程,要么运行一个能同时绑定多个套接字的版本。
2.7 之前一个进程只绑定一种传输
社区版 OpenVPN 绑定 proto 所描述的套接字,而该指令为实例只选择一种传输。TCP 与 UDP 是两套独立的协议栈,因此同一个端口号可以同时被两者占用,但不可能由一个进程同时占用。所以 2.6 系列只能是每个守护进程一种传输。OpenVPN 2.7 取消了服务器上的这一限制,一个进程可以通过多条 --local 语句绑定多个套接字。
在围绕任一答案做设计之前先确认版本,因为 2.7 的方案是另一套运行模型,有自己的配置语法。为 2.6 写的指南即使不说,描述的也是两个进程。
运行两个实例的三种方式
使用 TAP 接口时,为每种传输各运行一个实例,并在主机上把两个 tap 设备桥接起来。两组客户端落在同一个广播域里,因此一个地址池就能覆盖它们,依赖广播的协议也能继续工作。使用 TUN 时无法桥接,所以最简单的划分是让每个实例拥有自己的子网,由路由把两组客户端分开。第三种方案保留单一地址池:每个实例调用 learn-address 脚本,通过该实例的设备为客户端安装 /32 路由。
第三种方案正是人们最常要求的,也是最需要小心的,因为脚本成了数据路径的一部分,必须正确处理添加、更新和删除事件。
| Arrangement | Interfaces | Address pool | Cost |
|---|---|---|---|
| Bridged TAP pair | Two tap devices joined by a bridge | One shared pool | Broadcast traffic and bridge maintenance |
| Two TUN subnets | Two tun devices, no bridge | One pool per instance | Routing rules and two plans |
| Shared pool with routes | Two tun devices plus a hook | One pool across both | A script on the data path |
让两个实例彼此隔离
每个实例还需要各自的端口或地址、各自的状态文件和各自的日志去向。两个守护进程写同一个状态文件,故障时输出的内容根本读不了,而那恰恰是你最需要它的时候。如果第二个实例绑定某个特定地址,进程启动时该地址必须已存在于主机上;否则它会在提到密钥之前就以绑定失败退出。
给单元和配置文件起名,让文件名就能看出它用哪种传输。下一位运维不该为了知道哪个是 TCP 而打开配置文件。
让配置文件与监听器匹配
运行 proto tcp 的服务器期待带 proto tcp-client 的客户端配置文件,而仍带 proto udp 的配置文件在这个监听器上 会超时,且不给出用户能据以处理的错误。如果部署提供两种传输,就保留两份配置文件,并做好标注,让支持人员知道试过哪一份。仅凭端口号并不能说明传输类型。
防火墙规则也遵循同样的划分:针对 UDP 1194 的规则不覆盖 TCP 1194,针对一个地址的规则也覆盖不到另一个监听器。
先读服务器侧的视图
先在服务器上执行 ss -lntup,确认 TCP 与 UDP 监听器都出现。然后每种各连接一个客户端,检查状态输出里是否有两条地址不同的记录。如果套接字存在却没有客户端进来,那是路径或防火墙在守护进程看到报文之前就把它们拒绝了。
从不出现的套接字是配置或绑定问题,出现了却从不记录连接的套接字是过滤问题。从客户端看这两者完全一样,所以要先看服务器自身的视图。记住 TCP 带有队头阻塞,因此提供它只是一种可达性手段,对 UDP 路径干净的客户端并不是升级。