OpenVPN优化之-TLS握手控制通道的建立

关于OpenVPN数据通道的一次优化正在进行中,当我参考了”巨型帧“的概念和思想后,我仔细思考了TCP/IP协议栈的设计和实现,于是我得出了一个可能是错误但是起码在我的场景中很实用的结论:上层协议尽管把数据发出吧,不要管数据的大小,如果真的需要拆分,那就由下层来完成吧。
       这个结论的一个反例就是TCP协议的数据分段,它感知了传输链路的MTU,也许在早期的网络中,IP分片重组确实影响了TCP的端到端流控和拥塞控制,也许还为状态防火墙带来了难题,但是现在不了,现在绝不再是这样了。如果在应用层感知了MTU,那么增加的处理复杂性完全抵消了IP分片避免带来的好处。

OpenVPN协议分析

我在3年前曾经逐字节地分析过OpenVPN协议,那个时候几乎没有人分析过,我的分析也只是兴趣所致,没有什么实际用途。看着OpenVPN那凌乱的代码,然后通过抓包分析协议实在太痛苦了,我当时有一种编写OpenVPN协议Wireshark插件的冲动,若不是受制于Windows以及Gnome/QT编程环境,也许我早就实现了,不喜欢也玩不转IDE,旧梦让我急于将时间快速用在问题本身而不是其它任何事。虽然当前的编程工具,框架几乎都在声称”让你将精力集中在自己的逻辑,而不必在意XXYYOO“,但我看不到这一点,学习成本太高本身就抵消了你对外围的关注。为了一杯牛奶养一头牛本身就是一种愚蠢的想法。
       Wireshark对OpenVPN协议的支持最终还是来了,但是来的太晚了。
       抓取OpenVPN隧道建立时的数据包,确认SSL如何在OpenVPN协议内部被封装。显然,SSL握手协议需要被封装在OpenVPN协议内部,具体如何封装的,抓包结果如下图:




这个图本应该显示了ClienHello的封装,但是呢?没有但是,它就是ClientHello,你看到数据的前几字节:16030100...折腾过SSL协议或者被SSL协议残忍蹂躏过的一看到这个就没脾气了。但是为何OpenVPN协议没有解析出这是一个ClientHello呢?因为它分段了...这么一个短短的ClientHello也要分段?也要!OpenVPN协议头占据了一些空间,加上ClientHello本身,我们看到这个OpenVPN数据的长度为100,它的另一部分在数据段29里面,那么看一下29:




确实,数据段28和数据段29拼接起来就是ClientHello了,是这样吗?当然是!
       为什么一个ClientHello要分割成100字节的大小呢?早在3年前我在一篇文章中写下了”这样,即使一个1000字节的SSL握手消息,reliable层也可以将其拆成10个100多字节的UDP包,数据到达对端后,每一个100多字节的UDP包进入其SSL BIO的内存BIO中,由SSL协议的实现负责重组数据。“那篇文章是《OpenVPN协议解析-握手数据包分析》,当时费了那么大的劲也没有给出原因。
       纵一个ClientHello都要分割,要是ServerHello以及后续的Certificate呢?当然也要分割了。我们看一下这个夸张的包,分了那么多的段:




UDP下的握手性能

从抓包分析可以看到,一次SSL握手要来来回回交互那么多的包,每一个包在Reliability层都要经过确认,这将极大影响效率。为何不将分割处理交给下层呢?IP层或者网卡会做得更好,即便做的不好,你也不必加班调试自己的程序和排错,对于协议栈的故障,你只需浏览Maillist,更新驱动...
       在测试之前,我先要找出在哪个地方将发送段的长度设置成了100。对于OpenVPN,有两个frame,一个是数据通道的,一个是控制通道的,这次我们只关注控制通道。这个frame在tls_multi的tls_options字段中,它的初始化在tls_init_control_channel_frame_parameters:
static void
tls_init_control_channel_frame_parameters(const struct frame *data_channel_frame,
                      struct frame *frame)
{
...

  /* set dynamic link MTU to minimum value */  //WHY?WHY?说了多少次了,两端的MTU要一致!而这个是最可能一致的!毕竟控制通道不建立,一切无法协商!
  frame_set_mtu_dynamic (frame, 0, SET_MTU_TUN);
}

我们把最后这个调用中的0改为1500或者更大的任何数即可!它直接影响了tls_process的过程,在tls_process中,SSL握手消息存储在内存中,每次IO的粒度是PAYLOAD_SIZE_DYNAMIC (&multi->opt.frame),而它就是frame_set_mtu_dynamic设置的结果。
       在修改了控制通道的MTU之后,再次运行,数据包的个数几乎就是SSL握手的数据包个数!比较一下前后的性能,修改后好了几乎一倍!我的测试方式比较简略,就是在x_msg里面加了一个计数器,获取毫秒值,SSL握手完成后计算时间差。
       原生代码为何要将控制通道的MTU设置为100呢?因为两端的MTU值必须一致,在控制通道建立之前,一切无从协商。

测试时的吐槽

我通过OpenVPN传输大数据,旨在计算一个性能值,作为对比,我关闭了OpenVPN进程但是并没有撤去机器,之前运行OpenVPN的机器只做forward,传输同样的大数据。这时有人说话了,而且不止一人,他们非要撤去两台本来运行OpenVPN的机器,非要将测试机用网线直连!有这个必要吗?就你那点数据还指望能压满路由器?如果Linux的转发机制能让你的那点儿数据压出个区分来,那世界上多少设备得下架啊!难道不知道,这世界上又有多少设备在真实环境中是网线直连的,难道要把交换机砸烂?难道路由器交换机的发明是个错误?
       我明白非研发人员的心理,他们当然要挑出你的哪怕一点点毛病,毕竟他们要直接面对客户,这对于研发来讲也是好事,可以精益求精,之所以要撤掉设备做直连是因为他们想做出最大的差别。但是也麻烦专业一点,如果不知道一台设备的纯转发性能,就不要总嚷嚷它是瓶颈,如果一个软件有问题,那是用肉眼都能看出来的,如果不是排错而是求精,麻烦给出数据。如果真的那么希望做差别,麻烦别总是使用虚拟机,我本身是十分讨厌虚拟机的,特别是做压力测试时。
       一边吵着要撤掉还挺高端的纯forward设备,一边还坚持在笔记本上跑好几个虚拟机进行测试...唉

盲写socket代码

敢问谁能不看man,不google,不baidu,...直接盲写出一个TCP socket服务端,不用select/poll,如果能的话,请试试写一个select,接着poll,最后epoll...反正我一个也不能...

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。