热门标签 | 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性能




推荐阅读
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
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社区 版权所有