什么是TCP并发
TCP并发是指一个服务器同时“hold住”的连接数量,确切的说就是指服务器端看到的“ESTABLISHED”状态的TCP连接数量。通过netstat -n|grep ^tcp|awk '{print $NF}'|sort -nr|uniq -c可以查看当前服务器TCP状态统计报告,下图是我的执行结果(我正在通过SSH连接这台机器所以有一个“ESTABLISHED”状态的TCP连接)
测试TCP并发就是指让这个值达到的顶峰,要实现这个必须满足两点:
- 短时间内构造百万级连接
- 服务器端同时hold住百万级连接
需要注意的是上面的“测试”不包括“连接之后的交互”仅仅是指“hold住连接”。
传统工具为什么无法满足
很多服务器都是TCP结构的比如Mysql、Tomcat、Nginx,这些工具也有相应的压力测试工具,比较著名的包括:Jmeter、Tsung。这些工具的实现基本上是一致的
- 同时启动多个任务
- 每个任务打开一个socket连接到服务器
这种测试方法受限于三个资源
- 可以启动的任务数量(线程数或者进程数)
- 可以打开的socket数量(文件描述符)
- 受限于本机可用端口最大值——65535
第一个限制我们可以通过“协程”之类的技术手段解决;第二个限制在内存满足的情况下可以通过调整系统参数解决(参考我的《你真知道“Too many open files”?》);第三个限制几乎是致命的——传统上只能通过多台服务器一块协同。
即便解决了上述三个问题也很难在“短时间”内造成巨大的压力,大量的socket会吃光内存,多台服务器协同必然是一个分布式问题(想想就掉头发)。
新的思路
TCP连接给人的感觉是一个“通道”,这其实这是一个“错觉”。所有的网络基本上都是基于“存储转发”的经过。三次握手之后的TCP连接到达“ESTABLISHED”状态,服务器会为它保留资源——即使客户端已经不再理睬这个连接。那么我们是不是可以不经过TCP/IP协议栈直接通过raw socket构造三次握手呢?只要我们大批量的构造三次握手就可以对服务器构成巨大的压力了。
我们重点关注Client->Server的两个箭头。第一个数据包是SYN数据包,seq=随机数;第二个数据包是ACK数据包,ACK=收到数据包的seq+1,seq=收到数据包的ack。其实TCP数据包之间是没有直接关系的,我们收到一个数据包就可以直接算出回复数据包的ack、seq
上面的思路基本上可以证明我们的方法在理论上是可行的,在实践上我们还需要克服一些问题
- 怎么获取Server到Client的SYN+ACK(三次握手中的第二个箭头);毕竟我们不是直接使用Socket打开TCP连接(这样做就不需要自己构造TCP三次握手了)
- Server在收到Client请求后会尝试对Client进行ARP地址,如果发现无法解析就认为是一个非法的数据包直接发送RST数据包关闭TCP连接
第一个问题我们通过libpcap“旁路”kernel,直接获取原始数据包。下图是libpcap的原理
libpcap底层使用的是BPF(Berkeley Packet Filter)驱动,这是kernel中专门用来“调试”的驱动程序最早是为Unix开发现在已经成为各种操作系统的标配(只要支持tcpdump那么底层一定是有实现这个驱动模型的)。它独立于kernel中的其他协议栈直接和读取数据链路层的数据包。
通过libpcap我们可以获取所有的数据包(即便操作系统不处理)然后构造自己的数据包通过raw socket直接把写入到数据链路层。整个“收包”->“处理”->“回包”完全不需要kernel参与。
第二个问题其实在前面的文章中我已经给出了答案——构造并且回复ARP数据包(《深入理解ARP攻击 》)。简单来说就是通过libpcap获取arp request,通过raw socket回复arp response。
动手
我努力去掉所有不相关的东西只保留了最精简的部分,不到300行的代码。代码分为两大部分“发起TCP SYN数据包”和“回复SYN+ACK数据包、ARP request数据包”。具体内容可以看这里
这次我特意放上了cmake文件,执行以下cmake就可以编译了。
https://github.com/fireflyc/million-tcp-client
模拟多个IP地址
受限于端口上限,一台服务器只能模拟65535个TCP连接。但是我提供的演示程序是可以指定IP地址的,这个IP地址只需要和目标IP在同一个网络内就可以了。比如我的测试环境:
测试机器有自己的IP地址172.16.46.128,但是这个IP地址并没有用途,只是为了方便我SSH连接。服务器的IP地址是172.16.46.133,我启动三个tcp-client分别绑定200、201、202。
每个TCP-Client进程都可以模拟65535个TCP连接。(这个其实还有改进的余地)
【本文是51CTO专栏作者邢森的原创文章,转载请联系作者本人获取授权】