这里相对openswan的性能做个简单的说明。为什么要介绍这个话题呢?
其实最主要的原因还是想openswan的性能到底如何、极限是多少隧道、会有哪些瓶颈等等? 比如某个项目,客户需要设备支持1000条隧道,那么首先要考虑自己的产品能否支持到这么多?也就是说需要知道自己的实力。如果不考虑这些实际的问题,只一味的接项目,那么最可能的结果就是白白投入这么多的人力物力时间,最终对于公司来说收效甚微。
另一个原因:openswan的官方也没有给出明确的性能参数,比如说最大能支持多少条隧道? 所以根据现有的硬件资源来测试其性能是必不可少的。具体问题具体分析嘛。
pluto能支持多少条隧道
这个问题,我是众里寻他千百度呀,可惜的是蓦然回首,产品经理他不走。后来我在openswan的源码中找到有这么一条邮件信息,原文如下:
FreeS/WAN allows a single gateway machine to build tunnels to many others. There may, however, be some problems for large numbers as indicated in this message from the mailing list:
Subject: Re: Maximum number of ipsec tunnels? Date: Tue, 18 Apr 2000 From: "John S. Denker" <jsd@research.att.com> Christopher Ferris wrote: >> What are the maximum number ipsec tunnels FreeS/WAN can handle?? Henry Spencer wrote: >There is no particular limit. Some of the setup procedures currently >scale poorly to large numbers of connections, but there are (clumsy) >workarounds for that now, and proper fixes are coming. 1) "Large" numbers means anything over 50 or so. I routinely run boxes with about 200 tunnels. Once you get more than 50 or so, you need to worry about several scalability issues: a) You need to put a "-" sign in syslogd.conf, and rotate the logs daily not weekly. b) Processor load per tunnel is small unless the tunnel is not up, in which case a new half-key gets generated every 90 seconds, which can add up if you've got a lot of down tunnels. c) There's other bits of lore you need when running a large number of tunnels. For instance, systematically keeping the .conf file free of conflicts requires tools that aren't shipped with the standard freeswan package. d) The pluto startup behavior is quadratic. With 200 tunnels, this eats up several minutes at every restart. I'm told fixes are coming soon. 2) Other than item (1b), the CPU load depends mainly on the size of the pipe attached, not on the number of tunnels. 1234567891011121314151617181920212223242526272829303132333435
It is worth noting that item (1b) applies only to repeated attempts to re-key a data connection (IPsec SA, Phase 2) over an established keying connection (ISAKMP SA, Phase 1). There are two ways to reduce this overhead using settings in ipsec.conf(5):
- set keyingtries to some small value to limit repetitions
- set keylife to a short time so that a failing data connection will be cleaned up when the keying connection is reset.
The overheads for establishing keying connections (ISAKMP SAs, Phase 1) are lower because for these Pluto does not perform expensive operations before receiving a reply from the peer.
A gateway that does a lot of rekeying – many tunnels and/or low settings for tunnel lifetimes – will also need a lot of random numbers from the random(4) driver.
上面的信息是2000年的一个邮件,回复的是freeswan的问题,但是这是我目前找到的最接近的回答,即使它也没有给出明确的答案。
我私下测试pluto性能,隧道最多的时候建了4800条左右(峰值),此时左右两端隧道的状态已经严重不一致了(left已经建立成功,但right却没有),这个原因我还没有细究,不过可以肯定的是:和pluto的低性能有关。如果不考虑因移植引入的瓶颈问题,支持5000条应该不是问题。但此时的pluto的效率已经很低很低。下面我把自己遇到的问题做一个简单的记录、分析。
pluto的瓶颈在哪里
既然说pluto的性能比较低,那么它的瓶颈在哪里呢?
这个原因会有很多,因此pluto的可用配置参数很多,不同的配置时,瓶颈略有不同,但是有几个函数,无论策略如何配置,它的CPU占有率都是稳坐前几把交椅的存在。(在很多隧道时,特明显,比如多于1000条)。下面我来一一说明:
openswan_log()
openswan_log()
这个函数支持为了增加调试信息以及记录日志信息。由于需要频繁的进行系统调用(或读写文件),导致pluto的处理性能极大的降低。当然对于va_start(),va_end()
等函数并未细致研究,因此它的效率根源暂时无法回答,可以肯定的是它的效率真的很低很低,因此为了优化这个问题,只需要在openswan_log()
的实现中实现一个开关即可,如果不做任何日志操作,直接返回即可。解决这个开关常见的有两种方式:
- 添加一条whack命令
这是因为whack命令已经实现了进程间的通讯(whack—pluto),完全可以满足要求。此种做法处理效率快,也更加正规。
- 访问文件系统中的一个文件
我们也可以借助文件系统实现进程间通讯。例如判断文件系统中是否存在某一个文件。这里便可以通过检测文件系统中是否存在特定文件来控制openswan_log()
是否记录日志信息。
此方法的优点是实现很简单,实时性很好;但问题是每次需要访问文件系统,效率比直接访问全局变量来的慢。
find_phase1_state()
在隧道协商过程开始之前,需要确定是否之前是否已经协商过且存在状态,如果存在,可能第一阶段已经协商成功,直接进行第二阶段协商即可。这个过程是必须的,通过它可以确定发起第一阶段协商还是第二阶段协商。那么这个函数都做了哪些操作呢? 从当前的状态哈希表(statetable), 找到最适合当前连接的状态。也就是说需要将整个哈希表遍历完才能找到“最合适”的。
除此之外,什么是"合适呢"?
那就是一系列的判断条件,其中一个是same_peer_ids
, 最终比较双方的ID标识,这个标识如果是默认的IP类型,那么速度会相对快些;但如果为FQDN、USER_FQDN,那么函数的执行效率会更低。
因此如果我们提前添加几千条条隧道,每协商一条隧道,find_phase1_state
便至少执行一次,遍历所有的状态,检测所有的条件,那么随着隧道协商的增加,效率越来越低。
目前对于这个的优化还没有特别好的想法。一个初步的想法是“以空间换时间”:即同时维持两个哈希表,除了上述的哈希表外,再维持一个基于连接的哈希表,两个哈希表共享所有的节点,只需增加一个指针域,将其链起来,构成一个网状的结构。使用时根据不同的需求访问不同的哈希表,速率上应该能得到提升。(增删节点时得同时操作两个哈希表)
event_schedule()
pluto中对于定时事件的处理是通过排序链表(LFU)实现的,使用排序链表有一个缺点:当定时事件很多时,添加新定时器的效率会很低。在插入操作时,我们需要将当前的时间在链表中进行排序,虽然时间复杂度是O(n),但当定时事件很多时,却不得不考虑效率低下的问题。这在pluto中添加成百上千条隧道时,体现的尤其明显。那么有没有办法进行优化呢? 答案肯定是有的。
常见的定时器有三类(《Linux高性能服务器编程》):排序链表,时间轮,事件堆。
既然排序链表低,很多人可能想到哈希表:根据定时的时间长度来取哈希,然后在进行排序岂不是可以提高效率。不错,的确这样,这就是时间轮的基本思想。如果需要优化的话pluto中的升序链表可以改为时间轮。那么改为时间轮效率能提高吗?由于pluto中的定时器都是以秒为单位,这个时间跨度感觉有点太短,导致多个定时器堆积的问题,但是比升序链表效率高是确定的,因为在添加定时器时的效率提高了很多。
con_by_name()
这个函数是在根据连接的名字查询连接时使用的,pluto是使用链表来维护的。如果数量很大时,遍历链表的时间复杂度O(n)。当然这个可以进行优化,同样采用哈希表,尽可能的将连接均匀的分布在不同的表中,这样便可以提升查询时的效率。
generate_msgid()
msgid是第二阶段时的一个重要参数,它用来唯一标识一个IPSEC SA。
在生成msgid时,要确保msgid的唯一性,因此需要遍历当前ISAKMP中所有的msgid。这个效率低下,跟我配置隧道参数时相关:我配置的隧道的端点IP相同,也就是说所有的隧道共用一个ISAKMP SA, 因此第二阶段的隧道生成msgID时,需要同所有的msgid进行匹配,如果重复需要重新生成。
pluto中核心架构分析
pluto的核心处理架构是在call_server()
中实现的。它使用IO多路复用的方式将IO事件,信号,定时器进行的集中的处理。该处理方式的优点在于统一处理,编程实现上容易些(虽然对源码的作者来讲:so easy!!!)。 如果这里将这三个事件分别放到三个线程中进行处理,当添加的隧道数量没有那么多时,效率上有一定的改善。可能问题来了? 如何判断多线程(多进程)和IO多路复用孰优孰劣呢?
IO密集型和计算密集型
首先得考虑业务的类型,属于IO密集型还是计算密集型。
- IO密集型
I磁盘的读取数据和输出数据非常大的时候就是属于IO密集型。由于IO操作的运行时间远远大于cpu、内存运行时间,所以任务的大部分时间都是在等待IO操作完成。
特点:cpu消耗小。(因为CPU在休息等待)
IO密集型,由于CPU很多时间处于休眠等待期,因此通过多线程(多进程)来同时执行多个IO操作,只要其中一个没有阻塞等待,那么效率就可以得到相应的改善(不考虑资源切换和非常多的线进程情况下)。
- 计算密集型 计算密集型就是计算、逻辑判断量非常大而且集中的类型,因为主要占用cpu资源所以又叫cpu密集型,而且当计算任务数等于cpu核心数的时候,是cpu运行效率最高的时候。
特点:消耗cpu。
很明显,虽然IPsec也有比较多的IO操作,典型的就是使用数字证书进行协商时,但它绝对称得上是计算密集型(加解密就是啪啪啪一顿计算,CPU直接满负荷工作)。这时即使才用了多进程多线程技术,效率上也有可以没有办法得到提升。此时最好的办法就是提升硬件的性能呢。
pluto适合多线程多进程嘛?
pluto虽然属于计算密集型,吃CPU的大户,但是如果将上述的三个事件(IO事件,信号,定时器)分开处理,自测情况下情况有所改观,协商的隧道数量提升了几百个左右。但是如果想把IO事件(IPSEC协商流程)进行多线程多进程处理,效率上应该得不到明显的提升,毕竟CPU已经满负荷的在计算,又让他去计算另一条隧道,理论上改善的可能性有限。
解决办法就是增加相应的硬件辅助资源:如增加CPU核心数、增加硬件加密卡等。
后来我的观点是:pluto中不能使用多线程/进程, 因为存在很多全局变量,对于共享资源加锁并不是一个很好的选择。