热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

动手写一个OpenVPN的wrapper来优化OpenVPN性能

OpenVPN,一个让人想说爱你不容易的VPN,曾经耗费了我大量精力的VPN,其性能,...最终还是不咋地!以下是一个大致的统计数据:


OpenVPN,一个让人想说爱你不容易的VPN,曾经耗费了我大量精力的VPN,其性能,...最终还是不咋地!以下是一个大致的统计数据:





纯千兆环境,4核心至强3.0GHZ处理器,OpenVPN使用BF-CBC加密,SHA1摘要,OpenVPN不绑定特定CPU,带宽可达20-30MB/s;




纯千兆环境,4核心至强3.0GHZ处理器,OpenVPN不加密,不摘要,OpenVPN不绑定特定CPU,带宽可达40-45MB/s;




纯千兆环境,4核心至强3.0GHZ处理器,OpenVPN不加密,不摘要,OpenVPN绑定特定CPU,带宽可达45-55MB/s;




纯千兆环境,4核心至强3.0GHZ处理器,OpenVPN使用BF-CBC加密,不摘要,OpenVPN绑定特定CPU,带宽可达35-40MB/s;




纯千兆环境,绑定OpenVPN到特定CPU,该CPU会跑满,带宽无法提升受制于CPU;




纯千兆环境,不绑定OpenVPN到特定CPU,没有一个CPU跑满,带宽无法提升受制于OpenVPN的单进程模型以及操作系统多处理器调度开销;




百兆环境,无论如何,OpenVPN的加密隧道带宽基本接近物理网卡的百兆带宽。





既然是OpenVPN的软件模型远远跟不上硬件的提升,那么就要想办法以量取胜,办法是运行多个OpenVPN进程,每一个CPU上绑定一个,通过Linux的内核接口可以很容易做到这一点:




1.首先mount一个cpuset,创建3个set



mount -t cpuset none /mnt
mkdir /mnt/{vpn1,vpn2,vpn3}



2.简单配置一个cpuset



echo 0 > /mnt/vpn1/cpus
echo 0 > /mnt/vpn1/mems
echo 1 > /mnt/vpn2/cpus
echo 0 > /mnt/vpn2/mems
echo 2 > /mnt/vpn3/cpus
echo 0 > /mnt/vpn3/mems



3.以不同端口号和虚拟网段启动3个不同的OpenVPN进程



openvpn --config cfg1 --port 1234 --server 121.121.0.0 255.255.0.0
openvpn --config cfg2 --port 2234 --server 122.122.0.0 255.255.0.0
openvpn --config cfg3 --port 3234 --server 123.123.0.0 255.255.0.0



4.绑定上述3个openvpn进程到不同的cpuset



i=1;for pid in $(ps -e|grep openvpn|awk -F ' ' '{print $1}');do echo $pid > /mnt/vpn$i/tasks; ((i=i+1)); done



5.将多个客户端连接在这不同的三个OpenVPN进程上,拉取大文件,测试OpenVPN服务器的tap0-tap2的总流量




经过上述配置后,新的统计数据如下:




纯千兆环境,4核心至强3.0GHZ处理器,OpenVPN使用BF-CBC加密,SHA1摘要,带宽可达35-40MB/s;


纯千兆环境,4核心至强3.0GHZ处理器,OpenVPN不加密,不摘要,带宽可达75-80MB/s;


纯千兆环境,4核心至强3.0GHZ处理器,OpenVPN使用BF-CBC加密,不摘要,带宽可达50-55MB/s;


纯千兆环境,3个CPU会跑满,带宽无法再提升受制于CPU;


纯千兆环境,将物理网卡中断(同时也是协议栈接收处理的软中断)钉在第四个CPU上,上述值再有5左右的提升。




可见,多个处理进程会带来带宽的大幅提升。实际上上述的方案完全可以通过虚拟网卡bondding或者多VPN进程共享虚拟网卡的方式做到,然而这些配置都不简单,有时还要修改虚拟网卡的驱动程序,可扩展性很不好,因此简单的以多OpenVPN实例来说明问题是再好不过的了,如需bonding方案或者shared-tap方案,请参考《
关于OpenVPN文章的目录
》。


虽然上述方案解决了部分性能问题,然而配置还是比较复杂,能不能通过一个wrapper将上述的操作包装起来呢?既能做到多实例并行-绑定特定CPU,又能做到OpenVPN客户端动态自动获取OpenVPN服务器端口号和虚拟网段。这需要增加一个层次,就是在OpenVPN连接之前增加一个“端口/虚拟网段”协商的层。该包装器的使用如下:



服务器端:openvpn-wrapper -t server -p 1111 -v /usr/sbin/openvpn -c /home/zy/cfg ....


客户端:openvpn-wrapper -t client -p 1111 -a 192.168.1.23 -v /usr/sbin/openvpn -c /home/zy/cfg-client


参数意义如下:


-t:类型,分为server和client,和openvpn一样


-p:wrapper服务器监听的TCP端口号


-v:openvpn的路径


-c:openvpn配置文件的路径,该文件中没有那些自动协商出来的配置,比如openvpn的端口,虚拟网段等等。



openvpn-wrapper的代码结构如下:

struct vpn_opt {
char vpn_path[255]; //openvpn的路径
char cfg_path[255]; //openvpn配置文件路径,配置文件不含server指令和port指令
int port_base; //起始端口号
int num_process; //openvpn进程数量
int num_cpu; //CPU数量
int sintr_cpu; //单独分配的处理物理网卡软中断的CPU
};
struct vpn_opt vopt;
int vpn_servers()
{
int pid;
int ps = vopt.num_process;
//创建ps个openvpn服务器进程
while (ps--) {
pid = fork();
if (pid == 0) {
//每一个openvpn服务器进程绑定在一个CPU上
sched_setaffinity(getpid(), ..., ps%vopt.num_cpu);
execve(vopt.vpn_path --config vopt.cfg_path --port vopt.port_base+ps --server $不同虚拟网段...);
} else if (pid > 0) {
} else {
}
}
}
int accept_clients()
{
//创建TCP服务器,接收client请求,回送端口信息
//端口调度算法:
//1.轮转调度:N个客户端按照先后顺序在vopt.num_process个进程之间轮转
//2.空闲优先:纪录M个VPN进程之间的负载情况或client连接数,取最小值
//3.其它的算法...
//对应客户端的wrapper先连接这个TCP server,得到VPN端口信息后调用exec启动
//openvpn客户端进程。
}
int main(int argc, char **argv)
{
char c;
while ((c = getopt (argc, argv, "")) != -1)
switch (c)
{
case 'v':
strcpy(vopt.vpn_path, optarg);
break;
case 'c':
strcpy(vopt.cfg_path, optarg);
break;
//可供配置的其它参数,比如CPU数量,中断处理均衡等
default:
abort ();
}
//下述参数必须由命令行提供:
//vopt.port_base = 61195;
//vopt.num_process = 2;
//vopt.num_cpu = 2;
vpn_servers();
accept_clients();
}

有了这个wrapper,就可以将之直接替代openvpn了,但是对于大网对大网的拓扑,如此混乱的虚拟网段让客户端怎么管理呢?这可以通过将所有的配置全部集中在OpenVPN服务器端来解决,客户端需要怎么配置全由服务器端来推送,涉及到自定义信息的推送,请参考push setenv-safe这个指令。如果涉及到全网互通的路由配置,你就不能单靠OpenVPN的client-to-client了,还要在不同的虚拟子网之间配置路由,怎么办呢?还记得ICMP Redirect吗?统一配置默认网关(注意单加物理网段互通的路由)该虚拟网段的OpenVPN服务器的虚拟IP,如果目标属于同一个OpenVPN实例管辖,那么tap模式下OpenVPN服务器会直接发送ICMP Redirect,如果不是由同一个OpenVPN实例管辖,那么确保OpenVPN服务器上拥有管辖目标网段的OpenVPN实例即可,一切都可以靠单点路由配置搞定。


总之一,将配置集中于一个点,最终由同一个人来配置,这样最不容易引起混乱,剩下的全部由机器来做。能推送下去的尽量推送下去,做到单点配置。OpenVPN提供了丰富的可推送的配置,实在满足不了的可以使用setenv-safe这个。


总之二,优化无极限,如果你单看多个OpenVPN实例运行带来了性能提升就沾沾自喜了,那么你就会错过绑定单个实例到一个CPU上带来的进一步性能提升,如果你有幸看到了这一点,不要停步,看看top输出,你会发现软中断可能在和OpenVPN抢夺CPU,因此你会进入/proc/interrupts看个究竟,于是你可以通过设置/proc/irq/smp_affinity文件来分离中断,突然,你看到了以下信息:



95: 18250769 193538 28997 45831 0 0 0 0 IR-PCI-MSI-edge eth3-TxRx-1




96: 4115 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth3-TxRx-2




97: 52535493 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth3-TxRx-3




98: 75459635 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth3-TxRx-4




99: 4115 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth3-TxRx-5




100: 44074216 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth3-TxRx-6




101: 20545603 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth3-TxRx-7



于是你不得不拿起千兆以太网卡的手册看看多队列相关的内容。这样完了吗?虚拟网卡带宽之和已经接近物理网卡的千兆带宽了--98MB/s,不要止步,记得Linux有一个renice命令,提升一下OpenVPN的优先级试一下...105MB/s。我想还可以更好,只是时间来不及了...出事了!


如果你只是看到了这,那么你可能错过了一件大事。重新看上面的eth3-TxRx-1信息,然后思考结果。当我将每一个OpenVPN绑定于特定的不同CPU之后,所有的网卡TxRx队列却还只是由一个CPU处理,这就形成了一个多对一的关系,考虑如下的拓扑:


动手写一个OpenVPN的wrapper来优化OpenVPN性能


服务器端每一个VPN进程由一个单独的CPU来处理,它们很显然都会将包发往网卡ETH3,而该网卡ETH3的中断却只由一个CPU来处理,不管是发送中断还是接收中断。能不能均分一下任务呢?在均分之前,先要想一个方案。我有8个CPU核心,分别处于两个封装上,每个封装4个核心,于是我启动6个VPN实例,分别绑定在编号(从0开始)为1,2,3,4,5,6的CPU上,然后让CPU0和CPU1各自处理一半的软中断(大多数是接收中断的软中断),这是因为一个封装内部的Cache亲和性以及CPU核心亲和性要比属于不同封装的核心好很多,因此两个封装中各自都有一个核心处理软中断,可以高效地将数据踢给绑定于同一封装核心的VPN实例。测试下来虽然性能提升不显著,但是毕竟有了3MB/s的提升。


说了这么多,所谓的OpenVPN的优化都是在OpenVPN外部进行的,要知道其本身也有很多的参数可以调节性能,比如sndbuf和rcvbuf以及txqueuelen这三个参数,如果rcvbuf和sndbuf不一致相差太多的话,会造成UDP以及ICMP的大量丢包,虽然TCP能调节自身的速率,但是当rcvbuf小到比TCP管道最细的部位还要小的时候,丢包就显著了,比如rcvbuf是M,而TCP的慢启动阀值为N,且N远大于M,这就会导致TCP频繁大量丢包,然后慢启动,然后再丢包,再慢启动...此时要么调小慢启动阀值,要么调小TCP发送缓冲区,对于Linux则是tcp_wmem,不过最好的办法就是调整sndbuf和rcvbuf,将其调整为一样大,并且适当比TCP管道更宽一些,让TCP可以在其中自由发挥流控以及拥塞控制,而不是将OpenVPN模拟成一段及其恶劣的线路,如果你将sndbuf设置成了30000,将rcvbuf设成了1234567,那么虽然TCP也能进入VPN隧道,但是这条隧道太恶劣了,有效速率将会很低很低。


可是还有个小问题,为何不把中断均分到VPN实例所在的CPU呢?这是可行的,然而必须用测试结果说话,有一点可以确定的是,如果那样的话,虽然可能会得益于千兆网卡的DCA,然而VPN实例和软中断将会抢夺CPU,抢夺的激烈程度不仅仅受制于CPU的性能,还要受制于时间的串行性的本质,毕竟同一时刻一个CPU只能做一件事,因此有时候,你用top发现每一个CPU都没有泡满,然而性能却反而因为中断均分而下降了,值得注意的是,Cache亲和性虽然很重要,但是相比访问同一个核心的Cache的开销,访问同一颗封装的Cache的开销也不会差太多,毕竟现代超猛的多核心处理器的每个封装,甚至封装之间都有共享Cache的。于是这又扯到了一级Cache,二级Cache,...内存,磁盘,网络等存储设备的大小和层次问题了...


通过测试终端的TCP重传次数计算,上述的结果非常不错,优化无极限,如果考虑到一路统一MTU,那将必须又是一个优化切入点,另外还有e1000e千兆网卡驱动的很多参数还没有调整,另外,如果你的CPU核心在绑了一个OpenVPN实例且满数据跑时top显示其idle百分比仍然很高,那么就在其上绑定两个或者多个OpenVPN实例,总之,CPU利用率达到90%以上并不是坏事,而是好事,这个和桌面系统是完全不同的...


如果CPU跑不满,先别急着绑多个VPN实例,还有一招,那就是压缩,在OpenVPN配置中增加comp-lzo即可,非常简单,理论和测试结果均证明,启用压缩可以大大减少丢包率,且使得吞吐量得到大幅提高。最基本的一点,对于TCP而言,我觉得多个OpenVPN载荷包打包压缩要比单个载荷包单独压缩效果更好些,OpenVPN隧道一端对多个TCP包进行压缩,另一端简单进行解压缩,隧道途中,压缩包中载荷数据包顺序决不会乱掉,因此也就使得串行的协议比如TCP的顺序性得到了加强,多包加密隧道不但封装了数据加密了数据,还带着串行的TCP数据走过了最坎坷的一段路使它们不会乱序。下图说明了这一点:


动手写一个OpenVPN的wrapper来优化OpenVPN性能


但是,即使是单包压缩,也会使隧道传输速率提高,因为隧道内的数据包尺寸减小了,更有利于传输(轿车要比卡车快...?),同时通过调节中途设备的网卡驱动参数还能将隧道传输速率进一步提高,举个例子,Intel千兆网卡就有可以调节的参数使它更有利于小包的收发,或者更有利于大包的收发,或者折中。MTU导致的IP分段也是使用压缩的重要理由。压缩可以将包压小,如果传输文件,发送数据块而不是小包的可能性较大(相反的则是ssh之类的延迟敏感的程序,它们一般使用小包通信),那么起始端可能根据网卡的MTU来截取数据包,然而OpenVPN在用户态使用socket为其进行封装,肯定会超过MTU值,因此OpenVPN的封装对于端到端的MTU发现是不可见的,所以IP分段并没有发生在端到端的载荷包上,而是发生在OpenVPN的数据传输本身。通过抓包,发现大量:



10:07:26.243655 IP (tos 0x0, ttl 64, id 50897, offset 0, flags [+], proto UDP (17), length 1500)


172.16.2.1.61195 > 172.16.2.2.43050: UDP, length 1529


10:07:26.243659 IP (tos 0x0, ttl 64, id 50897, offset 1480, flags [none], proto UDP (17), length 77)


172.16.2.1 > 172.16.2.2: udp



因此很大一部分开销花在了IP分段/重组上,故采用压缩是明智的。一个等价的解决方案是不使用压缩(毕竟它消耗了CPU),取而代之的是将OpenVPN两个端点之间的所有链路的MTU调大那么一点点(多处一个OpenVPN协议头以及OpenVPN使用的TCP/UDP头等协议头)。


做到这一步,现实意义上已经够了,剩下的就全属业余爱好了...


最后要说的是,虽然以上的方式有效的提升了OpenVPN构建的VPN隧道性能,然而却不能将所有多条隧道的带宽全部供给一个OpenVPN客户端,只是说它们的和是一个不小的值,如果想实现单条隧道的带宽提升,那就需要多实例bonding或者多实例路由负载均衡了。本文所写的其实不是什么创新,很多Linux发行版自带的OpenVPN本身就提供了多实例OpenVPN并行的配置,只需要service包装命令启动一下即可。


动手写一个OpenVPN的wrapper来优化OpenVPN性能




推荐阅读
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了将mysql从5.6.15升级到5.7.15的详细步骤,包括关闭访问、备份旧库、备份权限、配置文件备份、关闭旧数据库、安装二进制、替换配置文件以及启动新数据库等操作。 ... [详细]
  • 本文介绍了如何清除Eclipse中SVN用户的设置。首先需要查看使用的SVN接口,然后根据接口类型找到相应的目录并删除相关文件。最后使用SVN更新或提交来应用更改。 ... [详细]
author-avatar
手机用户2502917905
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有