OpenVPN UDP 的 Nginx stream 中继:改变了什么
UDP 不属于 HTTP 的 location。把它移到 stream 层,为 VPN 选择合适的会话超时,并预期代理会成为源地址。
客户端设为 UDP,Nginx 服务器有一个指向 VPN 端口的 location 块,但什么都到不了。这是意料之中的:HTTP 的 location 无法承载 UDP 数据报,无论前端叫什么名字。Nginx 需要为此使用 stream 监听器,而一旦把配置移过去,第二组细节就决定了会话能否存活。
代理层先于地址
Nginx 的 stream 模块在不解析 HTTP 的情况下代理 TCP 和 UDP。它的 HTTP 模块期待请求行、首部和响应,而这些都不存在于原生 OpenVPN 数据报中。因此选择哪一层是第一个决定,它与监听器是 IPv4 还是 IPv6 无关。
一个看起来能配合 TCP OpenVPN 工作的配置,对 UDP 情形毫无说明。在这里传输方式是协议的一部分,而不是部署的细节。
监听器与后端
nginx -V 2>&1 | tr ' ' '\n' | grep stream
stream {
server {
listen [::]:1194 udp reuseport;
proxy_pass [2001:db8::20]:1194;
}
}
在动手修改之前,先确认构建包含 stream 模块;发行版软件包可能把它拆成单独的模块,而缺少模块会产生配置错误,而不是隧道。把该块放在主配置上下文中,而不是 http 内部,并把文档地址换成真实的后端。
两条路径和两道防火墙都必须放行数据报:通往监听器的路径,以及从代理到后端的路径。IPv6 监听器也不会创建 IPv4 路径,所以如果仍有客户端使用 IPv4,那个监听器就是一条独立的条目,需要单独测试。要从代理的地址检查后端自身的防火墙,而不是从你的笔记本。
会话超时不是请求超时
stream 代理为 UDP 会话保存状态,并根据自己的超时和响应设置将其丢弃。从 DNS 中继照搬的短超时并不适合预期要交换数小时数据报的 VPN,故障表现为连接悄悄死掉,而不是拒绝启动。
读取部署中实际的 proxy_timeout 以及任何响应计数设置,然后与 VPN 实际使用的 keepalive 行为作比较。不同的人在不同的文件里写下的两个数字,是系统正常工作的常态。从未被比较过的两个数字,是系统间歇性出问题的常态。把两者都写进服务记录,让比较只发生一次,并且是在白天。
中继改变了什么,没有改变什么
stream 代理不会终止 OpenVPN 的 TLS 控制通道,也不会把它转换成 HTTPS。后端仍然是 VPN 服务,证书关系仍介于客户端和该服务之间。保持预期的后端身份仍然是客户端和服务器的事。
普通的代理同样会改变后端看到的源地址。日志会显示代理,VPN 服务器上任何基于 IP 的访问规则评估的都会是这个地址,而不是客户端的地址。透明地保留源地址是另一套路由与权限设计,并不是给监听器加上 udp 就会自动获得的东西。在写下依赖客户端地址的策略之前,先把它规划好。
从客户端验证,而不是从端口验证
在本地检查 UDP 端口已打开,只能证明监听器存在。这并不能证明客户端能通过它完成认证或持续交换流量。请从获批的客户端完成一次真实连接,然后确认会话保持的时间足够长,以至于有意义。
当有人说 Nginx 做不了 UDP,因为一个简单的中继工具反而能用时,这两件事其实无关。那个工具只证明了后端会应答,并没有说明前端支持什么。两者都可能成立,而其中只有一个才是适合这套部署的配置。