我靠,OpenVPN 你咋回事啊
人生必做的 114514 件事:成功调通 OpenVPN(1/114514)。
本文严格来说,依然算是网络小故逝系列的番外,这次来讲OpenVPN,VPN界的扛把子选手。
全文掺杂大量笔者的思考,如果你也是一个喜欢折腾网络的人,不妨进来看看笔者的胡说八道和天马行空。
OpenVPN 长期以配置复杂而闻名1. 前言
老规矩,先放系列文章:
- 几个网络小故逝(第一集):谁给ISP接网线,什么叫T1/E1
- 几个网络小故逝(第二集):黑宽带/民营ISP还在神州大地上行走的时候
- 几个网络小故逝(第三集):IPv6与BGP的乌托邦
- 几个网络小故逝(大结局):Cloudflare 的那些事
- KCP 与 SD-WAN:跨网比赛,今年起,正式开赛:就是字面意思
OpenVPN,玩网络的朋友应该对此并不陌生,不论你是干运维的,要连入机房管理网,还是出差/大学放假的,要连入公司/学校内网,亦或者是购买了大厂的服务用来上网的,都多多少少接触过这玩意。
长期以来,OpenVPN主要的名号在于:开源,久经审计,但速度较慢,而且配置复杂。就后两点而言,一方面是大厂VPN纷纷上马WireGuard,打广告的说法就是『极速上网,快如闪电』;另一方面是,如果你想自己搭建OpenVPN CE(社区版),你会发现网上存在相互冲突,且不知道多少年前的教程,也许你配不成功,也许能配成功,但首次开机之前还得蹲在那等他生成个几分钟的DH参数,连上一看还在用BF-CBC加密,列出配置文件一打就是一屏幕的证书。
所以,今天的网络小故逝,就来讨论一下这个让人又爱又恨的OpenVPN。
2. 千层饼配置
2.1 老资历的厚重
如果要区分一个VPN协议是否『传统』,笔者的习惯是按照能不能自定义端口来分,比如说PPTP,L2TP/IPSec,IKEv2/IPSec之类的,就比较传统;OpenVPN,WireGuard之类的,就不那么传统。但实际上,翻阅OpenVPN Changelogs可知,要真论辈分的话,首次推出于2001年5月的OpenVPN 0.9,其实比很多协议都要老一些,甚至传统中认为有板有眼的IKEv2(标准定义于2005年12月)也比他年轻不少,更不用说SSTP(2007年)之类的协议了。
第一份 Release NoteOpenVPN在此之后的发展,大家都有目共睹,因此不再赘述。但很显然,和形成标准后就固定不动的其他VPN协议相比,OpenVPN直到今天都还有更新,好处当然是可以随时间升级,引入更高强度/速度的加密方式,修复漏洞等;但坏处也很显然:可能引入代际不兼容问题,以及冗余配置问题,比如上图所示,OpenVPN刚发布时的默认加密模式是Blowfish,在现在新版本上已经默认禁用甚至逐步移除了,早期配置和客户端自然也就无法连接。
另一方面,OpenVPN的版本号其实也有些迷惑人,看起来只动了小版本号,但其实跨度非常大。比如说,2.3版本发布于2013年的1月,那时候笔者还在还在背乘法口诀表,第二天老师还要抽背唐诗;而到了2.6版本,就已经是2023年的1月25日(大年初四)了,那天笔者刚过完年在探望姥姥。你可以算算这中间差别有多大。
图源:endoflife.date加重这个问题的还有服务器本身的一些性质。网上大多数教程,由于历史惯性,都给基于CentOS 7或者Ubuntu 22.04这样的发行版来搭建的,考虑到以稳定为主,可能还会引入LTS版本,这就导致软件仓库中的OpenVPN版本依然很老,例如Ubuntu 22.04,笔者在网上看到过通过默认软件源安装的OpenVPN版本就有2.3.6和2.4.7这两种,如果你是用的CentOS 7,可能还会碰到2.2.2版本(笔者小学刚入学)的OpenVPN。
于是我们就会碰到:
- 新版本怕支持的不多
- 老教程还在被搜索引擎索引
- 生产环境还是老版本Linux
- 管理员怕改了引发崩溃
- OpenVPN的向后兼容性还可以(比如上面的Blowfish,也只是逐步移除)
- 本来就配置复杂,需要引入辅助脚本,但辅助脚本也可能多年未更新
- 部分路由器自带的OpenVPN,因固件未更新也是老版本
- 写教程的人,用老版本配置,『通了就走』
这些因素加起来,就导致依然有大量搭建教程还在炒冷饭,而且加热速度并不是太快。
更有甚者,因为配置相对较为容易,有朋友选择使用SoftEther VPN所内置的OpenVPN兼容服务器来搭建,这玩意对标什么OpenVPN版本,暂不清楚,但从上个月更新的README来看,他对于OpenVPN的一条注释是AEAD mode is supported in DE only.(DE指的是开发版),所以合理怀疑,稳定版里面的只会更老。
所以在2.9小节,笔者会给出一个扔掉了绝大多数历史包袱,在OpenVPN 2.6.3中测试通过的OpenVPN配置(也是作为第三大节的一系列测试实验的配置文件),并详细说明里面的每一个配置项的具体作用。不过在此之前,咱们先来看看,要手写一份配置文件,究竟难在何处。
2.2 到底啥模式哇
正如上面一节所说,OpenVPN诞生于2001年5月份。
在那个年代,菊花还只是一种花,南通还只是江苏的一个市,双子塔还竖立在纽约,看到好嗑的cp我们会叫做『荧幕情侣』,Linux内核大多数停留在2.2,甚至连Windows XP也仅仅存在于传闻中和几个测试版当中而已。
网络参差不齐,内核功能不多,TUN/TAP功能也是参差不齐,一切都是蛮荒的,OpenVPN所面对的就是这么一个环境,再加上OpenVPN的设计目标其实是场景驱动,又发展了这么多年,因此他的配置项就显得很多,各种配置的组合就更加多。
因此,我们先做一个限定,下面如无特殊说明,所有的配置文件决策,以及测试,均在此环境中进行:
服务器:
- 硬件:i5-13600HX
- 系统:Debian 12
- 环境:LXC in PVE(内核6.17.2-2-pve),分配2核心
- IP:10.0.0.160
- OpenVPN 版本:2.6.3(OpenSSL 3.0.17)
客户端:
- 硬件:i5-12600KF
- 系统:Windows 10 22H2 19045
- IP:10.0.0.11
- OpenVPN Connect版本:3.8.0(4528)
两者位于同一个局域网,采用千兆以太网互联,平均RTT为0.4ms,网内无丢包。同时,下文的部分OpenVPN输出信息,为了方便展示,手动做了换行处理,但并不改变其意义。
这里需要补充一点:目前的OpenVPN Connect,实际内核为OpenVPN Core 3.11.3,应该可以理解为C++重写版。但从官方GitHub页面来看,只实现了客户端(的库),至于服务端,目前还是得用原版OpenVPN 2.x。重写的目的未知,但社区Wiki(的机翻版本)提到『它更注重让系统上的普通用户(非特权用户)能够启动和管理自己的 VPN 会话』,个人猜测可能还会带来性能的提升。
从理论上来说,OpenVPN Core 3兼容2.x的协议,但考虑到可能存在的性能提升,因此下面的基准测试部分不排除数据略高的问题。考虑到下面的只是定性测试和直观演示,且配置方面没有发现不兼容的地方,因此决定保留OpenVPN Connect的测试内容。
此外多说一句,和OpenVPN Connect相比,OpenVPN Community版本体积真的小太多,但代价是界面略微不太直观,因此看个人取舍吧。至于笔者,反正除了这次测试之外几乎不用OpenVPN,所以没啥所谓。
2.2.1 子网 vs P2P
那个年代,设计VPN的主要目标,是在不租用物理专线的情况下,模拟出来一根虚拟专线,因此当时的工作模型是:
- VPN生成一条加密点对点三层链路
- 双端配一条(或几条)静态路由
- 原本指向专线的流量,被指向VPN接口
- 实现了安全通信
换句话说,他就是一根网线,两头插俩终端/路由器,至于流量怎么路由,不归VPN管,典型例子就是IPSec,如果玩过商用硬件路由器,华为的也好思科的也好,配置IPSec的时候,都需要你显式指定感兴趣流/ACL,写好路由,才能正常工作。
如果你有过搭建WireGuard的经验,你就会发现,这和我们今天认知中的,基于虚拟子网的VPN拓扑结构(VPN端点承担一部分路由工作)并不太一致。事实上,OpenVPN刚诞生的时候也只支持这样的点对点模式,即使2.x版本开始支持多个客户端连上来,在程序内部也只是模拟了成吨的逻辑P2P链路而已。我们今天所相对熟悉的Subnet拓扑,是在2005年9月份发布的2.0.2版本中才引入的,甚至1.x版本的Changelogs里面根本找不到subnet或者topology等字眼。
2.0.2 引入 --topology下文为了方便叙述起见,我们把这种模式叫做P2P模式,但为了防止混淆,需要注意的有两点:
第一点是,这里的P2P显然不代表只有两个端点可以加密通信,他更多的还是代表两个端点在网络内的拓扑属性,比如我们说『两台路由器用点对点专线连接』,显然,如果正确配置的话,路由器下面的设备也可以互相通信;
第二点是,虽然OpenVPN的--topology选项的参数值可以写P2P,但这里的P2P,指代的是『真的把TUN接口配置为点对点模式』,链路上可以按照RFC3021标准配置掩码为/31的地址段。而上文提到的OpenVPN一开始支持的点对点模式,按官方说法,其实应该叫net30模式,也就是使用/30掩码,四个地址,抽掉一个网络号一个广播地址,剩下两个可用地址,两端一人一个。
就目前来看,net30模式已经处于deprecated状态,他的应用场景已经可以被p2p模式取代,甚至如果你在2.6版本上配置为net30(或不明确配置拓扑结构),会直接弹警告:
# openvpn --config ovpn-server-net30.conf
2026-01-18 21:24:46 WARNING:
--topology net30 support for server configs with IPv4 pools
will be removed in a future release.
Please migrate to --topology subnet as soon as possible.
对于一般用途,subnet模式好处更多(提高地址利用率,减少路由条目数量等),所以如果没什么特殊需求的话,用subnet模式即可,我们也采用这玩意。
2.2.2 TUN vs TAP
这里的TUN/TAP,指的是虚拟网卡的类型。可能部分读者已经知道,切换到TAP模式的OpenVPN支持二层通信,也就是说你完全可以用他来当作虚拟的以太网连接,在上面跑组播,广播,DHCP什么的,都是没毛病的。
但问题就在于,你真的需要TAP吗?
超过九成的场合下,我们所需要的『专线』,『安全通信』都是建立在三层(IP层)之上的,因为我们的设备已经习惯了处理IP数据包,互联网也是在处理IP数据包,如果还开TAP模式完整地传二层包,除了拖慢速度之外,并没有什么额外的好处。为数不多能应用到TAP的场景,可能也就是要处理二层组播广播什么的,对一般用户来说,很难碰到。
所以对于这个问题,笔者可以很老实告诉你,如果你在纠结自己需不需要TAP模式,就说明你不需要TAP模式,用TUN就好,能满足你需求的。这里举一个很神奇的例子:某国外大厂VPN,居然在应用程序里面提供了切换到TAP模式的选项(见下图):
厂商名称已打码在厂商的官网可以发现,TAP模式的端口与TUN模式并不一致——然而这和TAP/TUN并没有什么关系,不存在说TAP能换端口而TUN不行。老实说,笔者没想明白他提供TUN/TAP切换的意义何在,如果有读者知道的话不妨告知一声?
端口不一致2.2.3 静态密钥 vs TLS
静态密钥,这是另一个老资历的东西了。
OpenVPN的第一个版本(0.9)只支持静态密钥。我们今天所熟知的,基于SSL/TLS的,需要证书的OpenVPN模式,其实是从接近一年后的OpenVPN 1.0才正式引入的:
这才有点正式版的味道长期以来,静态密钥的应用场景一般被局限在临时测试,小规模使用,以及规避DPI检测 —— 有传闻称,在静态密钥模式下,OpenVPN不再(也无法)协商加密密钥,直接开始传输加密后的数据,在外部看来就是在传输高熵随机流量。至于这个说法是不是真的,笔者本来挺想亲自抓包考证一下的,但是在目前的测试环境下,服务端倒是可以勉强起来,但也需要修改一堆配置,还会打印一堆警告信息,提示你所有的无 TLS 模式将会在2.7版本彻底移除:
# openvpn --config ovpn-server-static.conf
2026-01-18 16:53:18 DEPRECATED OPTION: The option --secret is deprecated.
2026-01-18 16:53:18 DEPRECATION:
No tls-client or tls-server option in configuration detected.
OpenVPN 2.7 will remove the functionality to run a VPN without TLS.
See the examples section in the manual page for examples of
a similar quick setup with peer-fingerprint.
至于客户端,折腾了半天,始终报错无法连接。我不知道是配置没写对,还是APP会额外注入一些辅助配置项,总之已经没什么动力再去进一步测试了。
看起来还是动了SSL所以很显然,2026年的今天,别搁这折腾静态密钥了,要论规避DPI你有更好的选择,要论配置简便请直接到2.9小节抄作业,或者按照官方的说法去尝试使用新的对端指纹模式,不论怎么看都好,静态密钥已经没有生存空间了,还是用TLS模式吧。
2.2.4 ncp-cipher vs data-cipher
如果你综合这几个版本的配置来看,关于如何设置OpenVPN的数据通道加密算法,就有如下几种配置项:
cipher <算法>
ncp-ciphers <算法1>:<算法2>...
data-ciphers <算法1>:<算法2>...
data-ciphers-fallback <算法>这就导致很多人想手写一份配置文件,却不知道怎么样使用这几个参数,又或者即使是使用了,也有可能是误用,或者因为优先级和向下兼容等原因,导致未能正确选择所需的加密算法。
开始叙述之前先一句话总结:在2.6版本中,你只需要使用data-ciphers和data-ciphers-fallback这两个,比如说,你可以写:
data-ciphers-fallback AES-256-CBC对,只写这一个都行,塞进去2.6版本的软件里能用的,而且安全性也还行。如果你确定不会有(包括)2.3版本之前的客户端连上来,也就是不存在『无法协商加密算法』的情况下,那么连这一项都可以不用写。
原因很简单:根据2.6版本的手册,data-ciphers如果不明确设置,其默认值是AES-256-GCM:AES-128-GCM,如果底层库提供了对 Chacha20-Poly1305 的支持,就会自动变成AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305,不需要管理员操心。当然了,因为列表中的第一个算法会被优先推送到客户端,因此如果你更钟爱某个算法,可以往前排。
现在就可以说说剩下那俩是咋回事了。
cipher:本来是用于在2.4版本之前,确定数据通道的加密方法的,因此写什么用什么。但在新版本当中,已被弃用,目前还保留支持,仅仅是因为在非TLS模式下还会用到他,但正如上文和手册所说,2.6版本在TLS模式下会忽略此设置,2.7版本直接移除非TLS模式,那么这个选项也没什么作用了。
ncp-ciphers:2.4版本开始,OpenVPN支持协商加密算法了,于是就用这个值来确定可用的算法列表。如果你觉得眼熟,是的,根据这个Patch,这个在2.4版本刚刚加入的配置项,就在2.5版本中正式更名为data-ciphers,就是我们上面提到的那个,所以你可以放心地替换为新命名。
2.3 哪来的 proto tcp 呐
虽然说在把可靠传输层包在另一个可靠传输层里面(如上网时使用的TCP,包裹在OpenVPN TCP中)并不是一个好主意,原因有很多,比如经典的TCP Meltdown以及队头阻塞问题。但如果你确实想用,OpenVPN也不会拦着你,在配置文件里面写明白就好。
问题来了,早年间部分OpenVPN教程,可能会出现这样的配置:
proto tcp
# ...其他配置而另一部分教程会写:
proto tcp-server
# ...其他配置我们结论很简单:第一种配置会引起混淆,已经不推荐了。也许这样的写法是早年间的配置方式,目前还保留兼容性支持,但保不准未来会引发什么问题,所以尽量避免。其实如果你翻阅2.6版本手册,你会发现proto项可选值只剩下3种:udp,tcp-server,tcp-client(当然还包括另外六种,用来强制指定使用IPv4或者v6的,因原理一样,不再赘述)。显然,并没有出现proto tcp这种写法。
但如果你真的这么写了,正如上文所说,在部分场景下系统可以正常工作,如这份配置:
proto tcp
server 10.68.25.0 255.255.255.0
# ...其他配置OpenVPN可以从第二行里面推出来,你想要创建一个服务器。但如果你去掉第二行,或者把第二行的server改为ifconfig,OpenVPN就无法再推断你想要的工作模式,会报错退出:
# openvpn --config ovpn-server-tcp.conf
Options error:
--proto tcp is ambiguous in this context.
Please specify --proto tcp-server or --proto tcp-client所以,在TCP模式下还是写明白tcp-server/tcp-client比较好。
2.4 还守着 RSA 嘛
想要安全通信,我们很自然能想到对数据进行加密和校验处理,而且你可能已经了解过,OpenVPN建立在SSL/TLS之上,而SSL/TLS混合采用了对称加密和非对称加密。所以这一小节我们先来讨论非对称加密。请注意,笔者不是密码学的专家,因此下面的内容不能作为严谨的密码学技术讲解来看待,如有需要可自行参阅其他资料。
计算机课的知识告诉我们,比较典型的非对称加密算法是RSA,它依赖于质因数分解问题高困难性。也就是说,你把两个大质数乘起来很简单,但如果给你一个数,让你拆开成两个质数的乘积,就显得非常困难。我们经常看到RSA-512,RSA-1024,RSA-2048这样的说法,后面的数字指的就是『那两个大质数』的乘积在二进制下的长度(称之为密钥长),单位为Bit。
计算机科学中尚未解决的问题:
整数分解问题,能否在经典计算机上,以多项式时间求解?
—— Wikipedia
虽然这个问题至今没有解答,但实际上,已经有不少团队通过暴力分解等方式,配合逐步提高的计算机算力,证明了可以在合理的时间内对一定长度的大质数进行分解。部分朋友可能听说过RSA 因式分解挑战,虽然官方已经在2007年停办,但依然拦不住人们继续尝试,目前的破解记录是成功分解了RSA-829。虽然说远不及目前推荐的加密强度,而且也并不一定意味着确实存在高效解法,但显然,既然破解记录正在被不断打破,如果要传递一些机密信息,那么还是越安全越好。
对RSA来说,要想提高安全性,只需要加长密钥即可,所以你会看到现在经常有说法『不推荐1024,至少2048,4096及以上更好』,便是这个原因。不过你也许已经知道了,加大这个数字意味着生成密钥对和加解密的速度都会变慢,尤其是生成密钥(私钥),这种慢是以指数形式递增的。为了直观演示,笔者在服务器上跑了一个测试:
# time openssl genpkey -algorithm RSA -out 2048.pem -pkeyopt rsa_keygen_bits:2048
real 0m0.166s
user 0m0.127s
sys 0m0.012s
# time openssl genpkey -algorithm RSA -out 4096.pem -pkeyopt rsa_keygen_bits:4096
real 0m1.668s
user 0m1.663s
sys 0m0.005s
# time openssl genpkey -algorithm RSA -out 8192.pem -pkeyopt rsa_keygen_bits:8192
real 0m15.208s
user 0m15.200s
sys 0m0.007s很显然,在长度达到8192的时候,生成时间已经来到了恐怖的15秒,这还是CPU单核性能足够强大的情况下测量出来的,如果说是低主频多核心的服务器CPU,恐怕生成4096都要花费不少时间。就算你能忍受这些时间,因为加长密钥就意味着密钥被加长(废话....),如果你真的执行了上面那些命令,现在请你把终端全屏,然后cat一下刚生成的几个私钥文件,你就会明白我说的是什么意思了。
# wc -l 2048.pem 4096.pem 8192.pem
28 private_key.2048.pem
52 private_key.4096.pem
100 private_key.8192.pem
180 总计为了解决这个矛盾,数学家和密码学家开始捣鼓,然后发现了一个非常好用的方案,叫做ECC(椭圆曲线密码)。因为笔者的数学功夫实在是不过关,甚至目前在备考专升本但已经确定要牺牲掉高数靠其他科目拉分,所以没办法具体解释其工作原理。不过很显然的一点是,ECC可以用较小的密钥长度实现RSA较大密钥长度的安全性。比如有一条曲线叫Curve25519的,其衍生签名方案Ed25519在256位密钥长的情况下理论上提供相当于RSA 3072的安全性。这个密钥小到什么程度呢,可以搓一个给你看:
# openssl genpkey -algorithm Ed25519 -out - | cat
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIEAHLueVF/8Qkl7XIf0SqGo9gHTvB8UlC5JaU464oYB7
-----END PRIVATE KEY-----
# 没了!就这么多既然密钥小了,计算速度也会快很多,后续扩展也会轻松很多,比如觉得256安全性不够,可以把长度提升到512及以上,对于CPU来说依然是微不足道的算力需求,但理论上就可以提供相当于RSA 15360的安全性。
所以这几年开始,新出/新升级的协议都开始转向ECC,毕竟又快又安全,谁不喜欢呢?
2.5 算啥 DH 参数啊
本来想不分节的,但写了一大半发现,还是分开比较好,毕竟要理解DH参数还是需要讲一下前置知识的。如果您想直接知道结局方案,请翻到2.5.3小节。
2.5.1 DH的作用?
上面讲的是非对称加密,而计算机课的知识告诉我们,非对称加密最大特点是速度慢,实际通信的数据量是很大的,非对称加密无法胜任。因此,我们可以用非对称加密来安全传递密码,再用这个密码,调用对称加密算法,进行高速安全通信。
虽然从理论上来说,这里使用『密码』一词并不严谨,但笔者确实不想每次都打『对称密钥』这几个字,因此这里做个定义。
那么问题来了,密码要怎么传递呢?计算机课上,老师可能会说,我们可以这么做:
- 随机生成一个密码
- 用对方RSA公钥加密,发给对方
- 对方用RSA私钥解密,获得密码
- 双方用这个密码,配合对称加密算法,进行加密通信
听起来很美好,而且理论上确实能用,因为攻击者只能监听到到公钥加密后的内容,又因为缺乏私钥而无法解密(这里暂时不考虑中间人攻击的问题,这是另一个层面上的安全考虑了)。但不确定你有没有想过一个问题:如果对方私钥泄露了,咋办?
显然,攻击者可以使用泄露的私钥,来解密上面传递的密码,进而窃听/篡改通信内容。这就是我们说的,传统RSA密钥交换不具备前向保密性(PFS),说人话就是,一旦私钥泄露,过往所有通信内容都可以被解密。
为了改进这个问题,一个较好的密钥交换算法(Key Exchange,简称KE)需要考虑两个方面:
- 不传输密码:直接不让密码(包括加密后的)出现在链路上,双方只能结合私钥等不公开的信息,进一步计算,才能得知具体的密码,窃听者光靠监听链路上的信息,根本无法获取密码
- 临时密钥对:每次进行密码交换时,都生成新的公私钥对,用完就扔,这样即使泄露一对公私钥,也仅仅是本次通信受影响而已,之前和之后的都不会有问题
这两点一结合,就出现了改进的KE算法,就是大名鼎鼎的DHE。这玩意是Diffie–Hellman 密钥交换的一种变体,靠DH解决了第一个问题,而末尾的E(Ephemeral,临时的),就解决了上面提到的第二个问题。
出于历史原因,DHE一般很少单独使用,一般和RSA组合使用,就出现了DHE_RSA算法。这种情况下:
- 服务端长期持有RSA密钥对
- 建立会话时,临时生成DHE密钥对
- 用RSA私钥对其(以及我们下面要提到的DH参数)进行签名
- 发送给客户端
也就是说,RSA的作用,从传输密钥,变成了身份验证,客户端只需要用正确的服务器公钥进行验签,就知道发过来的DH参数是否被篡改,服务器是否有人冒充了。此时泄露RSA私钥,风险就变成了『有人可以冒充服务器』,而并非『通信内容可以被解密』。
2.5.2 为什么要DH参数
从上文我们可以看到,要想通过DHE进行密钥交换/协商,服务器需要准备的东西,除了DHE密钥对之外,还有一个被称之为DH参数的东西,这就是本小节的重点。
继续阅读下去之前,不妨先开一个终端,挂一旁,执行这条命令:
# 熟悉命令的朋友,可能会注意到,我故意漏了一个参数,
# 当然这里先卖个关子。如果你知道漏了什么参数,
# 那么你也应该可以猜到,下面我想说什么
time openssl dhparam -out dhparams.pem 4096好,我们继续。
这里所谓的DH参数,其实包括两部分,一般被写成(p,g),后面的g表示生成元,一般比较小,因此我们可以忽略不管,现拿一个就行。重点来看前面的p,我们在OpenVPN中看到的设置项dh,nginx中被人遗忘的设置项ssl_dhparam,包括上面执行的命令所生成的东西,其实指的都是这个p,其形式是一个大质数。
DH算法就是依靠这个大质数,来和对端协商密钥的,你可以理解为这就是生产原料,少了这个原料就无法生产出密钥来。这些参数无需保密,甚至可以完全公开,所以上文才会说RSA仅仅用来签名,而不是把这一大坨东西加密起来。
不过,DH参数不是随便什么阿猫阿狗都能胜任的,除了必须是质数之外,最基础的要求就是足够长,它的长度直接决定了DH算法的安全性。这里的足够长是多长呢?自从2015年Logjam攻击被发现之后,这个长度就变成了至少2048Bit,如果对安全性有更高要求,就得是上面写到的4096Bit。
生成并验证一个大质数,是计算密集型操作,说人话就是需要耗费大量的CPU性能和相当长的时间。所以在正式应用中,应用程序不会在运行时才创建这玩意,而是需要管理员手动生成一份DH参数,填入到配置项中。此外,有一定数学基础的朋友都知道,目前人们还没发现有某个神奇的公式可以直接一步到位生成质数,因此目前普遍的生成方法是:
- 随机生成一个奇数
- 看看它是不是质数
- 如果不是,要么回到第一步,要么对这个数加2并回到第二步
不论是听起来,还是做起来,这个方法都很暴力,但事实就是如此。此外更惨的一点是,第二步的操作,虽然理论上可以用试除法,也就是2到sqrt(n)判断其能否整除n,但对于DH参数要用到的长度来说,试除法的效率是很低的,因此目前更多的还是采用类似Miller-Rabin素性测试之类的方法,来判断一个数字是不是『可能是个质数』,通过不断调整测试参数,如果反复多次都能通过,那么就可以认为这个数是质数了——当然,这依然不能排除实际上并非质数的可能性,不过计算机的世界里其实并没有那么多精确的东西,如果你真碰到一个通过了几十上百次测试,却发现最后结果是个合数的,笔者建议你赶紧去买张彩票。
看看你被你挂一旁的那个终端,出结果没?
如果没出结果的话,你应该能体会到『预先生成DH参数』这一步操作到底有多痛苦了,虽然不是你在算,但却实打实占用了机器性能和宝贵的时间。笔者写这段文字的时候,确实后台开了一个终端在跑这个,最后得到的结果是:
real 10m33.266s
user 10m33.166s
sys 0m0.044s冬日暖手宝,酸爽不酸爽?
最后,我们揭晓答案,上面漏掉的参数是-dsaparam。启用这个选项之后,OpenSSL会更换生成方式,产生所谓的『DSA风格』参数,并自动转换为DH参数,而这个过程比默认所采用的方式要快非常多,生成4096位参数,也只需要几秒钟时间,而且安全性并没有明显下降。
当然了,笔者连这几秒钟的时间都不想浪费,怎么办呢?
2.5.3 椭圆曲线,救我狗命
椭圆曲线这个好东西,再一次降临了。
这次他带来的东西是ECDHE,正如你所猜想的那样,DHE就是上面的DHE,前面的EC就是椭圆曲线的意思。
有了ECDHE之后,我们再也不用费力生成DH参数了,因为ECDHE的参数(椭圆曲线方程、基点、阶数等)是公开的,经过严格审计,而且非常简短,简短到可以内置于各个加密库中,且仍不失其安全性。
OpenVPN于2.4版本引入了ECDHE,我们用的是2.6版本,因此我们可以这么写配置:
# 这是针对TLS 1.2的,你可以加上或删除其他支持ECDHE的协议
# 执行 openvpn --show-tls 查看完整列表
tls-cipher TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA256
dh none指定ECDHE算法之后,DH配置可以直接写none,也不需要再费时费力生成DH参数了,好耶。
2.6 DCO 不挺好吗
OpenVPN长期以来被诟病的一点就在于他速度慢。根据其他博主的分析来看,他的慢有可能是因为线程问题(只能吃单核),也有可能是反复在用户态和内核态切换的问题,不知道具体是什么情况,可能两者兼备。
不过,在Linux Kernel 6.16,也就是几个月前,OpenVPN Data Channel Offload(简称DCO)被合入了内核中。从字面意思来看,DCO的功能就是把数据通道的加解密交给内核态来处理,理论上来说会提高一点性能(但据说还是不如WireGuard)。
为了测试这一点,笔者开了台VM(因LXC无法动内核),运行Debian 12,分别在有DCO和无DCO的情况下进行了对比测试。安装教程网上一搜一大把,这里不再赘述,顺带一提,DCO模块体积挺大的,安装时要注意。
接近800MB另外还要注意,PVE开UEFI虚拟机,会默认开启安全启动,导致DKMS无法安装。因为这里是测试环境,笔者选择重启进入固件,关闭安全启动,但如果你是在生产环境安装,建议按正确方法对模块签名处理后安装。
具体设置位置需根据不同固件而调整首先先看没有DCO的情况,CPU占用率还是挺高的(因为跑满了千兆内网),而且能明显看到是用户态进程在占用CPU。

接着去掉--disable-dco选项,并确保你所选择的加密方式(--data-ciphers)是DCO所支持的方式。开启DCO选项后,查看OpenVPN日志,应该能看到DCO版本号,以及打开DCO设备的提示:
如图所示启动DCO之后,htop看不到用户态在占用CPU,需要按下Shift+K,取消隐藏内核态程序,就可以看见不同了:
速度倒是没啥变化可以看到,总体的CPU利用率有所降低,去掉iperf3的占用之后就显得更低了。
所以DCO有没有用?有,其实还真的有点用,如果你在给你公司或者别的什么地方搭建OpenVPN,又或者你在性能较弱的设备上搭建,那么笔者还是建议你装一个DCO,要么能降低占用率,要么能提升速度,要么都行,还是挺好的。
2.7 IPv6 空间咋这么小?
现在是IPv6的年代,那么我们自然是想要让OpenVPN里面也能传递IPv6数据包的。
正好,你查了查文档,发现可以这么写:
server 10.68.25.0 255.255.255.0
server-ipv6 2001:db8:cafe:1145::/64保存好配置,然后启动OpenVPN,却发现控制台里面打印出来这么一条信息:
# openvpn --condig ipv6.conf
NOTE:
IPv4 pool size is 253, IPv6 pool size is 65536.
IPv4 pool size limits the number of clients that can be served from the pool看起来只是一条平平无奇的提示,但却很奇怪:为什么IPv6地址池空间只有65536呢,不应该是2^(128-64)吗?
其实原因很简单:OpenVPN的IPv6支持,是有人为限制的。
打开OpenVPN 2.4的Changelog,进去就能看到,2.4.10版本新增了一条限制,把IPv6的可用最大地址数量限制在了65536:
甚至还排在第一位为了佐证这一点,不妨来查看OpenVPN的源码,可以找到更为直接的证据。
定位到src/openvpn/pool.c,往下滚动到230行附近,可以看到这么一段代码:
pool_ipv6_size =
ipv6_netbits >= 112 ? (1 << (128 - ipv6_netbits)) - base : IFCONFIG_POOL_MAX;相对应地,在src/openvpn/pool.h中,我们可以找到这个常量的定义:
#define IFCONFIG_POOL_MAX 65536以上这段代码可以这么理解:如果前缀长度小于112,那么就被钳位在65536(正好就是2^(128-122)),大于这个数,才实际计算能用的地址空间。所以上面的示例地址2001:db8:cafe:1145::/64,实际上变成了2001:db8:cafe:1145::/112。
实际上,这个限制在Patch 1136中被引入,根据作者的说法,增加这个限制是因为本来IPv6的地址空间就老设计中的IPv4地址池限制在这个大小(如上文提示信息所见,限制以小的为准),但现在OpenVPN允许IPv6-Only,失去了v4的约束,容易引起malloc()调用异常,所以为了规避这个路径依赖问题,于是人为地加上了这个限制。
需要注意的是,此处影响的仅仅是可以分配给客户端的IPv6地址数量,而并非可以通过隧道传输的IPv6地址限制,也不是连接到IPv6 OpenVPN服务器的限制。
其实可以认为,OpenVPN在三层模式下对于客户端数量是有一定限制的,存在六万多个客户端的硬上限(每个客户端一个/32的v4,一个/128的v6)。不过老实说,你都六万多个客户端了,一人收10块钱都有六十多万,去招个人想想有什么好技术办法呗,别让OpenVPN硬扛,不然它和服务器早晚得炸一个。
2.8 咋和 HTTPS 不像呀
这一节其实严格来说和配置关系不大,但既然来都来了,我们还是提一嘴比较好。
上面我们说过,OpenVPN是基于SSL/TLS的,现在已知HTTPS也是基于SSL/TLS的,我相信应该不止笔者一个人想过,如果我把端口调节为443,再开TCP模式,那是不是看起来和HTTPS差不多了呢?
不只是一般人,VPN厂家也是这么宣传的不过很抱歉,答案是否定的。要想证伪这个问题,只需简单的抓包即可。
WireShark内置了协议识别器,虽然有时候要求是在特定端口上才会调用对应的识别器,比如本例中的OpenVPN,必须使用1194端口,但很显然,对于更高级别的DPI审查设备来说,显然不存在『只有1194端口才会被识别』这个问题。

从结果中可以看到,TCP三次握手之后,WireShark就被捕捉到了客户端发送的,首字节为操作码0x38的控制信息,再结合后续的一些控制信息,可以很轻松判定目前的流量为OpenVPN流量。
作为参考,我们再来看一个正常的TLS握手:

可以看到,客户端发送的首字节为0x16,表示TLS握手,随后就是标准的TLS 1.3 Client Hello,WireShark也能正确读取到SNI信息。
这个简单的小实验可以看出,光靠TCP 443,OpenVPN可以绕过简单的四层防火墙,比如部分神奇的网络只允许53/80/443的TCP/UDP流量的那种。但显然,无法骗过真正的DPI设备(不然他也不叫『深度包检测』了)。顺带一提,进行这个实验的时候,tls-crypt处于开启状态,且已经正确配置了密钥,这也刚好证明了另一个观点,即tls-crypt无法保护流量不被DPI设备检测到。
如果你确实想要用OpenVPN来做此项用途,倒也不是不行,但你必须权衡利弊。此部分内容会在下面的3.2小节详细讨论,在此之前,我们需要先有一个能用的OpenVPN环境。
2.9 最终配置
OpenVPN配置可以拆分为四大项:
- CA证书
- 服务器证书,客户端证书
- 服务器tls-crypt密钥
- 两端的配置文件
按照以下步骤操作,你就可以获得和笔者同样的测试配置文件,至于运行环境和OpenVPN版本等信息,上面最开始已经提及,此处不再赘述。
2.9.1 CA证书
OpenVPN基于SSL/TLS,这其中最重要的东西就是CA证书,它是我们其他所有证书的信任根。
由于VPN服务器在现实场景中一般作为安全接入点来使用(比如授权人员通过VPN接入内网),考虑到服务器会暴露在公网,所以理论上为了安全起见,以下命令不应该在OpenVPN服务器上执行,最好是需要一台单独的签名机,防止CA私钥意外泄露,导致攻击者可以借此签署证书来实现身份伪造。不过我们现在只是测试环境,所以没什么所谓了,下面的操作假定你都在OpenVPN服务器上进行,知道一下有这个概念就行。
OpenVPN附带了一个名为easy-rsa的脚本来辅助生成,但本着知其然还要知其所以然的观点,笔者采用了手动调用OpenSSL的方式来生成。所以需要注意的是:笔者不是制作证书的专家,因此虽然尽力而为,但下面的配置不一定保证能满足高安全性需求的场合,毕竟没有经过审计。如果您有此类需求,建议查阅OpenSSL文档或相关生成标准,或直接使用easy-rsa(注意相应修改配置,以便生成Ed25519证书)。
# 生成Ed25519私钥
openssl genpkey -algorithm Ed25519 -out ca.key
# 生成一份配置文件,这样可以省去命令行交互的麻烦
# 请相应修改dn小节的值
# 比如 CN 字段的值,可以换一个更有意义的名字
cat <<EOF > ca.cnf
[ req ]
distinguished_name = dn
x509_extensions = v3_ca
prompt = no
[ dn ]
C = OV
ST = OV
L = OV
O = OV
OU = OpenVPN
CN = OpenVPN CA
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:TRUE
keyUsage = critical, keyCertSign, cRLSign
EOF
# 最后利用上面的配置文件,对私钥进行自签名
# 因为此时是单机完成,所以我们直接用req命令出结果
# 设定有效期为3650天(10年),你可以根据需要进行调整
openssl req -new -x509 -key ca.key -out ca.pem -days 3650 -config ca.cnf执行完上面的命令后,你应该可以看到目录下出现了ca.key和ca.pem这两个文件,为了安全起见,再次强调,ca.key务必需要保密。
2.9.2 双方的证书
接着,我们使用已经准备好的CA证书,来生成服务器证书。
# 生成Ed25519私钥
openssl genpkey -algorithm Ed25519 -out server.key
# 生成签名请求(req文件)
# 同样,请酌情修改后面的信息,尤其是 CN 字段
# 因为OpenVPN可以配置为校验对端的 CN 字段
# 来确保自己连接到了正确的对端
# 理论上,你应该在生成了req文件后,把他传输到签名机,进行签名
openssl req -key server.key -new -out server.key.req -subj "/C=OV/ST=OV/L=OV/O=OV/OU=OV/CN=OpenVPN Server"
# 如果你是分开单独的签名机,下面的命令请在签名机运行
# 如果你和笔者一样一台机器搞定,那么继续运行即可
# 首先是生成extfile
# 这个文件的内容较为重要,我们将在下面详细讨论
cat <<EOF > server.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints = CA:false
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = openvpn.server
EOF
# 最后是签发证书
# 请酌情调整有效期等信息
openssl x509 -req -extfile server.ext -in server.key.req -CA ca.pem -CAkey ca.key -out server.pem -CAcreateserial -days 3650这里重点提一嘴上面的extfile,重点是要了解几个字段的作用:
basicConstraints:目前不是制作CA证书,所以必须设置为CA:falsekeyUsage:至少应当有digitalSignature,才能使用。至于keyEncipherment,有资料称对于ECDHE而言已经不需要这么操作,笔者未求证(因为也是后来才看到的),因此没有做测试extendedKeyUsage:简称EKU,必须为serverAuth,表示这是个服务端证书,OpenVPN是会校验EKU的DNS.1:这反而是最宽松的一项,OpenVPN默认不校验SAN,也就是不会校验实际连接地址与这里的对应关系。当然如果你确定有域名,写一致的也无妨就是了
执行完毕后,我们可以得到server.key与server.pem,同样,server.key需要保密。
有了制作服务器证书的经验,要想制作客户端证书也就很简单了,我们只需要修改:
- 文件名:换成client(或其他名字)
- CN字段:请务必务必务必确保每个客户端的CN字段都唯一,因为CN字段在OpenVPN中常被当作客户端或者服务器的逻辑ID,包括你看审计日志,下发策略,
client-config-dir的匹配,都是借助CN字段来完成的,而且OpenVPN默认情况下也不允许同一个CN的并发连接(duplicate-cn是危险选项),所以一定要做到不同设备不同CN。 - extendedKeyUsage:请修改为
clientAuth
同样地,执行完毕后,就可以看到客户端使用的证书与私钥了,请把他们安全地传输到客户端。每次新增客户端,只需要修改CN字段,生成一套新的客户端证书和私钥,就可以了。
2.9.3 服务器tls-crypt密钥
这个相对简单,只需要在同一个目录下执行:
openvpn --genkey --secret tc.key就可以了。
2.9.4 两端的配置文件
新建一个server.conf,填入以下内容(可以去掉注释):
# 网络选项,根据需要来填写即可
# 如果需要tcp,请改为proto tcp-server
proto udp
port 1194
dev tun
# 确保断连时不会反复开关tun
persist-key
persist-tun
# Linux服务器上请如此设置
# 就是告诉OpenVPN服务器,在运行后尽快退到低权限用户
user nobody
group nogroup
# 我们需要子网拓扑,并指定所用子网范围
# 注意这里的server配置项,其实就是告诉OpenVPN这里作为服务器使用
# 对于服务器的网内地址,默认是此范围内第一个可用主机地址
# 在本例中,就是 10.68.25.1,以及 fd07:19:10:29::1
topology subnet
server 10.68.25.0 255.255.255.0
server-ipv6 fd07:19:10:29::/64
# 此处指定我们的证书的路径
# 注意 dh 选项必须存在,否则会报错,设置为none即可
ca ca.pem
cert server.pem
key server.key
dh none
# 这是刚刚制作好的tls-crypt密钥
tls-crypt tc.key
# 我们要求最低TLS版本为1.2,且强制选择ECDHE-ECDSA系列算法
# 这样配置其实也兼容TLS 1.3客户端
# 因为1.3密码套件就三个,所以无需额外设置
tls-version-min 1.2
tls-cipher TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA256
# 只需设置回退即可,默认会使用AEAD系列算法的
# 有额外需求,再添加 data-ciphers 配置
data-ciphers-fallback AES-256-CBC
# 从理论上来说,只要你选择了AEAD加密算法,此处选项也没啥意义
# 但按文档说法,tls-auth会使用此值来确定算法
# 考虑到不确定tls-crypt是否有类似行为
# 以及上面回退到CBC这个非AEAD加密的可能性
# 因此保留此选项
auth SHA256
# 确定远端证书的EKU为clientAuth类型
# 理论上去掉这行不影响通信,但安全性可能略显不足
remote-cert-tls client
# 保活数据包,防止NAT映射取消等问题
keepalive 10 60
# 用于在 UDP 模式下,本端退出时通知对端
# TCP下,此设置会被自动忽略,所以问题不大
explicit-exit-notify 1
保存好以上文件,作为服务器配置。
接着,新建一个client.conf,填入以下内容:
# 如果没有明确设置 --pull 就需要加上 client 选项
client
# 下面的配置,含义同服务端一致
dev tun
proto udp
persist-key
persist-tun
# 远端地址与端口
remote 10.0.0.160 1194
# 一直尝试重新解析域名
# 本文件中服务器地址为IP,因此不影响
resolv-retry infinite
# 不要绑定到特定的接口/地址
nobind
# 为了方便起见,我们可以内联证书信息
<ca>
# 内联CA
</ca>
<key>
# 内联私钥
</key>
<cert>
# 内联证书
</cert>
<tls-crypt>
# 内联 tls-crypt 密钥
</tls-crypt>
# 含义同服务端一致
tls-version-min 1.2
auth SHA256
# 校验对端证书类型是否为serverAuth
remote-cert-tls server
# 校验服务器证书的CN字段,确保连接到正确的服务器
verify-x509-name "OpenVPN Server" name
# 日志级别
verb 3保存好此文件,然后安全地传输到客户机,导入到VPN客户端中,点击连接,你就获取到了一条安全快速的OpenVPN链路。
上面的搭建过程只是一个基础,如果有更多需求,如推送其他路由,用户名密码认证等,请参阅OpenVPN文档。
3. VPN 大战 弱网
搭建好一个能用的链路之后,自然是想整些活,看看他的实际表现如何了。
在服务端安装iperf3,监听在5201端口。同时,考虑到我们在很长一段时间内,上网流量都是以TCP为主,因此就用iperf3默认的TCP模式进行测试。
实际测试下来,其实收获颇丰,因为这是用一次切身实际的经历,来直观感受到了笔者之前的几个断论:
- 为什么在跨境链路上维持一条TCP连接不是件好事
- 为什么混淆层很多情况下都导致严重降速
- 为什么 OpenVPN TCP 不被推荐
当然需要注意的是,以上几点问题锅不在OpenVPN,甚至不在VPN,其实是TCP的常态,不论你是OpenVPN,WireGuard,IKEv2,甚至你真的去租用运营商的MPLS专线,或者裸纤,都会碰到这些问题。
3.1 基准速率
首先,我们需要在无干扰千兆内网下测量基准速度,避免因设备问题导致后期评分的瓶颈。
先看UDP模式,获得了挺不错的速度,八百多兆很不赖了。
UDP模式TCP模式速度也不错,这就排除了TCP相关代码存在bug导致所谓的『TCP慢』的可能性。
TCP模式不过,千兆无干扰内网毕竟还是太理想化了一点,现实情况下的互联网条件当然没有这么好,那又会怎么样呢?
3.2 如果在冬夜,一个数据包
结论放在前头:公网上的延迟抖动与丢包,才是导致所谓的『VPN慢』的真实元凶。
Linux下有个很好用的工具叫tc,是用来给数据包整形用的,正好可以用来模拟链路情况。为了模仿现实中的网络情况,笔者划分了4个等级:
- 10ms延迟,1ms抖动
- 75ms延迟,3ms抖动
- 150ms延迟,5ms抖动,0.5%丢包
- 180ms延迟,10ms抖动,2%丢包,25%丢包相关性
分别对应较好的省内链接(或广州到香港的极佳跨境连接),到新加坡的连接,到美国优化线路的连接,以及到美国拥堵线路的连接,来模拟常见的一些环境。需要注意的是,上面的几个对应概念仅供参考,实际情况下并非固定一一对应,比如你所在的地方WiFi信号很弱,那么即使是连接到省内站点,也很容易因为WiFi的问题导致延迟和丢包率飙升。
当然,还是免责声明:以下测试并非严谨,只能作为直观感受和定性分析使用,甚至笔者在模拟过程中也发现了类似问题,可能是因为服务端并没有针对性调优,导致测量出来的数据有点难反映生产环境的实际状况。如果您真的有实际部署需求,笔者建议安排一下实地测试。
3.2.1 勉强还行的UDP
在多数国外大厂VPN的宣传文案中,都能看到『UDP速度快,适合流媒体和游戏等用途』的说法,老实说,这个说法还不赖。
先上测试图:
『省内链路』,速度下降到150M附近,虽然不如千兆,但看个4K流媒体甚至4K原盘,还是没有一点问题的
10ms-1ms引入75ms延迟,可以明显看到前段有速度爬升的迹象,正好对应了内层iperf3的TCP的慢启动过程,最后速度稳定在40M上下,看高清视频还是没啥问题的。
75ms-3ms延迟提升,且引入丢包之后,速度开始变得不稳定,反复跳变,这就印证了之前关于TCP丢包重传问题的论点。
150ms-5ms-0.5%引入更严重的丢包和延迟之后,速度反而略有改善,这可能是因为还引入了25%的丢包相关性(更贴切真实链路),让内层TCP偶尔能争取到一小段不丢包的提速空间。
180ms-10ms-2%-25%3.2.2 惨不忍睹的TCP
如果说UDP还看得过去(最慢速度还能开网页)的话,坐稳了,接下来的TCP才是真正的好戏开场。
笔者在之前的文章里讨论过TCP的队头阻塞和他在高延迟高丢包链路里的瓶颈问题,现在我们让OpenVPN改成TCP传输模式,除了会引入这两个问题,还会引入更严重的TCP Meltdown问题。关于这个的理论,我们已经讨论了不少,今天补一个演示,感受一下实际状况。
先来看『省内链路』,外层TCP的情况下,速度已经开始下降得比较严重了。
10ms-1ms引入延迟,可以发现速度跳变更厉害,从4M到23M,而且慢启动的速度也拖长了不少。
75ms-3ms引入丢包,速度基本归零,打开网页困难。
150ms-5ms-0.5%恶劣链路环境下,速度跟上面差不多,也是勉勉强强打开网页的状况。
180ms-10ms-2%-25%如果你买过国外大厂VPN,不论哪家的都好,想必一定感受过这样的链路吧。即使速度能起来,也不要高兴太早,当你实际浏览网页的时候,TCP的队头阻塞还在等着你呢。
3.2 外层混淆?
长期接触这个的朋友,肯定多多少少听说过混淆(也有叫Stealth的)的事情,比如说OpenVPN over SSH/Stunnel/WSTunnel/obfs4。
比如这个,笔者在多年前用过不论是在哪里看到的资料都好,一般都会注明,『混淆层会减慢速度』,但减慢多少呢?
同样是先把结论放前头:可能比你想象的要多很多。
大多数混淆层,只能处理TCP流量,也就是说你得使用OpenVPN(或者其他任何一种三层隧道协议)的TCP模式,这就已经慢了一层。此外,还有一个猜测:此时OpenVPN无法感知到链路的实际状况,一直都是本地loopback到混淆器,可能内部的部分调优机制(如果有的话,笔者并未研读源码)不会工作,导致慢上加慢。
笔者手头并没有安装Stunnel或者WSTunnel,但SSH却是系统自带的。众所知之,ssh -L可以实现加密端口转发,所以混淆层的测试交给他来进行。
首先同样是基准速度,我们直接给他安排最恶劣的链路环境(180ms那个),来表示问题瓶颈不在SSH加密转发上:
SSH80多兆,还行吧,而且同样的恶劣链路环境,这个速度可以说是吊打上面的OpenVPN UDP。具体原因笔者并不是很清楚,但一个可能的方向是,此时Linux的TCP协议栈直接对外,能更准确地针对链路进行优化/观测操作,丢包也能更准确地反映到某个段,而不是封装后一丢丢一片。到底是不是这样呢?还望各位大佬在评论区指点小弟一二。
接着让我们打开OpenVPN,注意需要修改配置文件,把remote改为127.0.0.1,并加上route-nopull配置,或者明确添加指向服务器真实地址的静态路由,防止流量回环。
此外,这里略微放点水,把链路环境调整到75ms延迟3ms抖动。在上面我们测试裸TCP的时候,这里的速度大概是10到20Mbps,看视频有些勉强但也不是不行。现在我们再来看看加上混淆层会变成什么样子:
OpenVPN + SSH可以看到,有几个时刻速度直接归零了,即使不为0,速度也也只有1-2Mbps,变成了连看网页都很勉强,可能也就发个电子邮件的程度。同样的,这里为什么又会这么慢呢?笔者还是不清楚,只知道各方说法不一,但确实会慢,估计还是和TCP协议栈的感知有关。
由此可见,状况很差的链路上,本来三层隧道的效果就不算很好,如果外加混淆层,只会更差。这也就是为什么,近几年这样的方式应用得越来越少,更多的是在加密代理,把里面的数据拆下来再转发,中断了TCP语义——这也是部分企业级WAN优化器的工作思路,说明并非空穴来风。
4. 写在最后
感谢您阅读这篇长文,当然也感谢我的博客没有在撰写这篇文章的时候崩溃或系统错误。
其实OpenVPN的话题,笔者一直都想讨论,但苦于没有系统性的测试体系,主要是因为配置复杂,导致没有成功地搭建过一个可用的OpenVPN环境。其实直到今天,如果是没有什么非要不可的情况下,笔者还是会倾向于使用WireGuard,毕竟真的是太简单了,也太快了。这并不是说WireGuard没有缺点,其实有,笔者还写博文讨论过,但毕竟瑕不掩瑜,所以体验也会更加舒服。
先写到这里吧。
(完)
说到这不知道博主有没有看过谭泰锋的wireshark分析课 https://m.bilibili.com/space/676050680 感觉可以用来分析一下OpenVPN的工作方式
谢谢,确实没有,也许以后会去看看
为什么要用 OpenVPN Connect 呢,相比开源的社区版谁知道添了什么鬼东西(
另外,如果我没记错,OpenVPN Connect 用的是 openvpn3,一个正用 C++ 重写的新轮子。
如果你了解相关区别,能否在文章内指出?
谢谢,已补充,用OpenVPN Connect是因为我之前电脑就装了这个,而且有反复安装软件搞炸了各个TUN关系的经历,就没有重新安装Community
对于OpenVPN Core 3,之前确实没有详细了解,因为手机上的OpenVPN for Android曾经有过OpenVPN 3的支持,但当时功能较缺,所以一直以为只是某个没有正式发布的测试版而已
欸欸,openvpn3 重写的目的是作为库使用,OpenVPN 2 只能作为应用
那看来还是有不同开发方向的考量,不过很好奇当2.x继续发展下去,版本号要咋办,该不会一直停在2了吧