接近崩溃的边缘,今天这篇文章构思地点在医院,小小又生病了,宁可吊瓶不吃药,带了笔记本却无法上网,我什么都不能干,想了解一些东西,只能用3G,不敢 开热点,因为没人给我报销流量,本周末我只有一天时间,因为下雨,我还有一个晚上。了解了PF_RING之后,我迫切希望做一个实验,于是跑回家验证后再 回来。
事情的起因是这样的。
一共有4个问题
1.关于一个网络加速卡
前
些日子,接触到一款网络加速卡,插在PCIe插槽,卡上运行独立的Linux系统,通过PCIe和主机通信。这块卡号称其特点在于处理抓包的性能非常好,
抓包?是的,这就是说它适合做DPI或者透明防火墙了。厂商的技术人员亲自为我们演示了一个防火墙的功能,即不能访问优库网站,其它的都可以。让我觉得好
玩的是,他们的加速卡上有两个万兆光口,一个接一台可以上网的PC,另一个接出口交换机,PC上除了不能访问优库,其它的都可以访问,而两个万兆光口既没
有被bridge在一起,又没有IP,这块卡是怎么做到一个口进另一个口出的,不是网桥,没有IP,这简直就不是一个网络设备,那它是什么?
2.最近公司的一次测评
最
近,对公司的设备进行了一次性能测评,结果达标,但是个人觉得还有提升余地,只是测评事件发生在京城,本人没有亲历,只能遗憾,加之测试方并非那种酷爱技
术之人,搞的我们的人总是在配合他,结果可想而知,见好就收,他们是绝不会让我们将他们那悍猛的性能测试仪器作为玩具折腾的。
被测试的机器看起来非常猛,超过100G的内存,超过30核的x86
CPU核心,超过10G的网卡好几块,重得让人不想搬,丑陋的外观让人觉得就像是楚留香打扮成的要饭的,总之,很猛,很酷,可是对于软件,就不能说很猛
了,我一向认为,x86架构加上Linux协议栈的网络性能瓶颈在于串行处理和内核中的锁,协议栈的处理本身就是串行的,因此传统的Linux内核协议栈
绝对不可能将上述如此悍猛的硬件发挥到最高性能,这种组合一般都是提供给虚拟机使用的,如果你真的想搭配这种硬件,那还是别用Linux内核协议栈了,如
果你想在不使用虚拟机且运行WEB服务之类的传统应用的情况下将所有硬件性能发挥到极致,无异于天方夜谭,瓶颈根本就不在硬件,而在协议栈以及应用服务
器。
有破就有立,这才像话。
3.公司的那个NAS上网行为管理
如果
我在公司浏览了一个不太雅的网站,我当然不想让NAS纪录,否则那帮管理员一定可以随时看到,然后背后嘲笑我,实际上管理员都有这种癖好,毕竟在当代,对
于隐私,网管的权力是巨大的。后来我听说这玩意儿是串接在主链路的,作为一个比较懂网络且比较好奇的研发人员,一定想知道这玩意儿是怎么做到二层监控的,
如果说是镜像数据,倒是真没啥新意,关键这玩意儿并不是旁路设备,确确实实是一个串联的设备,也就是说数据包真的就是完全通过了它的处理逻辑,说实话,如
果它不想让你访问XXYY,而不仅仅是监控,它完全可以作为一个防火墙使用(对于集权的网管来讲,如果是我,我一定会放开所有的访问,只是监控,这样才能
看到更多好玩的)。这需要设备具有极强的线速,不是一般的Linux可以做到的,它是怎么做到的,怎么做到如此高性能的转发的,关键是它还是个二层设备。
4.VPN的透明性
这
事得从一次耻辱说起。去年我在实施我的VPN时告知客户我们的产品是一个二层设备,于是客户松了一大口气,因为这会省去他大量的维护时间,因为二层设备不
需要配置网络参数,其实我也是这么认为的,然而他错了,我也错了,实施完毕我兴高采烈地回家,在后来的一个月甚至几个月内,我被无数次的“网桥模式下的路
由问题”骚扰,于是我这个研发再次冒充实施工程师向客户解释,说“我们将感兴趣流量拉到了第三层,不感兴趣流量直接二层通过...”这句诗一样的话简直是
在打自己的脸,因为这明显属于事后解释,搪塞,为什么一开始不这么说呢,客户如果懂技术,会认为这明显在扯嘛...
从密集实施中抽出身来的间隙,我有点走火入魔,一直思考如何可以“完全透明”实现二层设备(有一次被客户损的,那叫一个爽,说深圳某公司的VPN就是一个
盒子,串到链路即可,什么都不用配置...),办法当然有,诸如Linux Netfilter queue/packet
socket机制,诸如在内核中巧探测数据包的“上一跳”,返回包自动封装MAC机制,总之看我2013年5月份后的博客吧,炫技的东西大大的有,可是没
有一个实用的,这就是走火入魔!因此,我需要一个正常的标准方案实现纯二层VPN技术。
关于这些问题的解释
以上是我曾经提出的几个问题,对于很多人来讲,这些都不是问题,实际上对于我来讲,这些也不是什么问题,但是对于很多人来讲,有必要为他们明个理指条路。
对于我下面的解释,我认为首先要先实现一个原型,然后才能涉及性能,因此我先说一般原理,再说性能调优。
一进一出的设备与PF_RING
上面的几个问题中提到的设备也好,卡也罢,除了问题2之外,都是一进一出的设备,问题2并不是这种设备,它可以被看作是一台服务器,并不是转发设备,之所以将它也放在问题中,是想通过它来谈一下性能调优的方方面面。
这种设备特别适合串接在链路上,因为它们的转发原则非常简单,根本就不需要查表,原则就是从一个口进来的数据包一定从另一个口出去,你可以把这种设备看作
一根昂贵的全双工网线。这种设备无疑是可以转发数据的,但是在转发之前,却还是可以做些别的事的,比如过滤掉某些流量,比如记录一些流量...有没有什么
办法可以让没有这类实际硬件设备的最普通的人一睹这类设备的行为呢?
有,使用PF_RING可以很容易构建一个,那么什么是PF_RING。最好的资料就是其网站介绍
,但是我还是要简单介绍一下。所谓的PF_RING实际上是一种数据包抓取/投放机制,它本质上的行为就是从驱动模块直接抓取一个数据包放在一个环行缓冲
区中,或者直接将一个构建好的以太帧投放到网卡驱动的发送队列,实际上它提供了一个数据包的直接获取路径,没有它,按照以往常规的协议栈逻辑,一个数据包
必须串行地经过协议栈各层的层层协议头剥离,才能暴露出当前层次的数据。和基于PACKET套接字的libpcap不同的是,PF_RING机制更为灵
活:
1.PF_RING采用mmap的方式将网络裸数据放在一个用户态可以直接access的地方,而不是通过socket read/write机制的内存拷贝;
2.PF_RING支持下面2.1到2.3三种方式将裸数据放到mmap到用户态的环形缓冲区以及2.4的DNA方式:
2.1.按照PACKET套接字的方式从netif_receive_skb函数中抓取数据包,这是一种和PACKET套接字兼容的方式,所不同的是数据包不再通过socket IO进入用户态,而是通过mmap;
2.2.
直接在NAPI层次将数据包置入到所谓的环形缓冲区,同时NAPI
Polling到skb对列,对于这两个路径中的第一个而言,这是一种比2.1介绍的方式更加有效的方式,因为减少了数据包在内核路径的处理长度,但是要
求网卡支持NAPI以及PF_RING接口(一般而言,NAPI会将数据包Polling到一个skb队列)。
2.3.和2.2相同,只是不再执
行NAPI
Polling。这就意味着,数据包将不会进入内核,而是直接被mmap到了用户态,这特别适合于用户态的完全处理而不仅仅是网络审计,既然内核不需要处
理网络数据了,那么CPU将被节省下来用于用户态的网络处理。这可能会将内核串行的网络处理变为用户态并行的处理。下面我会有图示,这里只是点到为止。
2.4.这是一种更猛的方式,唤作DNA支持的模式,直接绕过内核协议栈的所有路径,也就是说直接在网卡的芯片中将数据包传输到(DMA的方式)所谓环形缓冲区,内核将看不到任何数据包,这种方式和Intel的万兆猛卡结合将是多么令人激动的事啊;
以上2.1-2.3和2.4的方式,按照PF_RING的网站介绍,有以下图示所示区别:
让人多少有点遗憾的是,2.4的方式并不是可以随意使用的,根据其License,提供免费下载,但是以二进制形式提供测试版本的库,如果需要长期使用,需要购买解锁的代码。说来挺可怜的,因为人家也需要钱来继续将研究进行下去。
PF_RING的背后
很
多人都只是认为PF_RING只是一个高性能的抓包机制,提供本机的数据包镜像分析,实现网络审计,这只是按照传统的思路来解释的。更进一
步,PF_RING机制颠覆了网络中间节点解释数据包的方式。按照传统的观念,中间网络节点只能按照协议栈的层次一层一层地解析数据包,所谓路由器是三层
设备,交换机是二层设备,防火墙分为二层防火墙和三层防火墙...使用PF_RING的设备,它可以将数据包直接从网卡的芯片DMA到你机器上的内存,仅
此而已,然后你通过一个应用程序而不是内核协议栈来处理数据包,至于说你的应用程序怎么处置数据包,我来列举几个:
1.深度解析数据包,按照各种你可以想到的粒度来解析会话,然后记录审计信息;
2.提供高性能的入侵检测功能;
3.转发数据包,按照路由器的方式。但是不再仅仅通过查询路由表的方式进行IP路由,而是可以通过各种各样的方式,转发表完全由你自己定义,比如实现一个通用的SDN流表;
4.根据上面第2点的含义,你可以决定哪些包被丢弃,这就是一个高性能的防火墙。
相比协议栈的串行解决方案,使用PF_RING是一个更加高效的方案,不但高效,而且灵活。如果你拥有多核心的处理器,你甚至可以可以在用户态并行处理数据包的各个层信息,如下图所示:
PF_RING
迎合了现代高端网卡的多队列特性,这是实话,但是即便是不支持多队列的网卡,如果按照现如今的Linux内核协议栈来处理,假设中断特定的CPU(实际上
Linux的中断balance实在不敢恭维),咱就假定用的是一块不支持中断均衡的网卡,一般而言触发的软中断也会在该CPU上执行,你丝毫没有办法,
即使你有8核心CPU,你又能如何,可是对于PF_RING而言,由于可以在一块网卡上对应多个Ring,就可以将不同的流置于不同的Ring,甚至不同
的数据包置于不同Ring,然后每一个Ring由绑在特定CPU上的应用程序处理,这实际上就是将所谓的多队列上推了一个层次,如下图所示:
对于非转发设备,比如一个APP Server,也就是说流量到本地终止的情形,未来的架构也许是这个样子,按照下图所示:
看
上面的图,也许你会问,设备怎么就知道数据包就是发到本地的呢?事实上这不是这个设备的职责,你怎么知道我DMA到Ring
buffer的是一个以太帧而不是一个纯粹的HTTP数据包呢?总之,没有现形的东西,总让人不可思议。PF_RING最让人爽的不是它实现了什么,而是
它提供了让你实现某种事情的机制,至于你能在PF_RING的框架内实现什么,完全受限于你的想象力,这也是为何很人人只是认为PF_RING只能抓包的
原因。
传统的误区
和在SDN环境下问“SDN交换机如何处理IP层的路由”这种问题一样,
我也向问题1里面提到的网络加速卡提供商索要所谓的”用户态的协议栈“,正是因为以往网络协议都是在所谓的协议栈中被处理的,所以即使对网络的处理移到了
用户态,我仍然需要有一个用户态的协议栈才安心,网络在用户态被处理也好,在内核态被处理也罢,关键要有一个栈,这样才算是网络的正常处理方式。然而我得
到的答复就是:如果你们需要自行开发协议栈,我们会全力配合和支持...天啊,自行开发协议栈,难道我要研读RFC,然后依照强制,建议等规程去写代码?
去处理复杂的IP路由,处理TCP状态机?处理TLS...难道没有现成的用户态协议栈?
实际上,PF_RING的思想不但颠覆了网络中间节点解释数据包的方式,也颠覆了数据包被处理的方式,它使网卡不再是一个网卡,它在协议栈上撕开一个口
子,即数据包不一定必须在栈里面被处理(事实上,早在PF_RING之前,很多二层防火墙就是这么做的,但是基于的技术并不是什么通用的技术,也没有通用
接口),栈本身就是一个串行的东西,一摞子盘子,如果上面的不拿掉,直接抽掉下面的,你可以试试。基于PF_RING实现的设备很多,比如网络审计系统,
比如防火墙,都没有使用什么用户态协议栈,因为它们根本就不需要协议栈,试想,为何需要实现一个栈,是为了分层解释数据包的各种逻辑,是一个解耦合的具体
实现,只有在异构网络互操作的时候,分层才是王道,如果说整个数据包你都拿到了,谁能规定你必须按照某种方式去解析去处理呢?你只要在接口层面保持和外界
网络一致即可,比如数据包在从你的设备出去的时候,一定是一个IEEE802.3的帧格式。
厂商没有给我用户态协议栈,实际上它们也没有,我也不想去实现一个什么协议栈,即便有现成的开源协议栈,我也不想去移植,即使这样,也照样能实现一些有用
的东西,实际上,忘掉协议栈吧,对于以太网而已,你能保证你可以正确接收和处理一个以太帧(IN接口),你能保证你发出的数据是一个以太帧(OUT接
口),这就够了,至于你是怎么处理数据的,没有什么标准。
BTW,上面我说的那一大堆关于用户态协议栈的问题以及关于那块网络加速卡的内容,都和PF_RING本身无关,但是思想一样。不管你有没有听说过,世界
上都存在一种Tilera-GX架构的超多核心处理板,而Tilera提供的NetLib套件则提供了一套非常便捷的开发接口,可以让你快速开发运行在用
户态的关于网络处理逻辑的一切,这个NetLib开发套件可以直接和其底层的Tilera核心完美契合。Tilera的思想是这样的,其板载的网络处理芯
片拿到数据包以后,不是交给内核协议栈(管它是Linux还是别的),而是放到一个环形缓冲区中,这个缓冲区可以直接被用户态访问到,到此为止,它基本就
是PF_RING
DNA的翻版,但是它有一套NetLib,而PF_RING机制与之对应的就是libpfring,然而看看文档和sample就知道,NetLib提供
的东西比libpfring更多,如果你使用libpfring,你需要有很好的想象力与构建能力,反之你使用NetLib,很多东西都是现成的(包括
IP路由查找逻辑),但是仍然需要你有一定的构建能力,不管怎样,二者都是相通的。
动手实现一个透明转发设备
以
上扯了很多的理论和感悟,外加一些PF_RING高性能的理由,事实上很多时候我们更关心应该怎么做,也就是说,更关心接口方面的东西,知道了怎么用,加
上你信任底层的实现没有问题,外带你对原理深入的理解,基本就无敌了(可是现实中大量的人只满足接口怎么用)。这个小节我尝试实现一个最简单的透明转发设
备,即一根昂贵的网线,最简单这个词意味着它只有转发功能以及一点点的过滤功能,目的在于展示接口的使用。
拓扑结构很简单,我的iMac关掉WIFI,网线连接我的Macbook,我的Macbook通过WIFI连接路由器,Macbook内置一个Linux
虚拟机,添加两块网卡,同为Bridge模式,一块Bridge到WIFI,一块Bridge到以太网,如下图所示:
如果只有一台电脑,这个拓扑还真不好搭建,别告诉我说使用VMWare的LAN Segment,我很讨厌那种东西。
以上拓扑的配置如下:
iMac-en0:192.168.1.200/24 default gateway:192.168.1.1
Macbook-en0:无IP地址
Linux VM-eth0:up 无IP地址
Linux VM-eth1:up 无IP地址
Macbook-en1:192.168.1.100/24(WIFI分配的,没办法) default gateway:无
TP-Link LAN IP:192.168.1.1/24
很清楚了吧。
接下来贴上代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <pfring.h>
- #include <string.h>
- #include <getopt.h>
- int main(int argc, char *argv[])
- {
- pfring *pfring_net1, *pfring_net2;
- unsigned char *dev1 = NULL;
- unsigned char *dev2 = NULL;
- char c;
- struct option opts[] = {
- {.name = "net1", .has_arg = 1, .val = ‘i‘},
- {.name = "net2", .has_arg = 1, .val = ‘o‘},
- {NULL}
- };
- while((c = getopt_long(argc, argv, "i:o:", opts, NULL)) != -1) {
- switch(c) {
- case ‘i‘:
- dev1 = strdup(optarg);
- break;
- case ‘o‘:
- dev2 = strdup(optarg);
- break;
- }
- }
- if(dev1 == NULL || dev2 == NULL) {
- goto end;
- }
- pfring_net1 = pfring_open(dev1, 1518, PF_RING_PROMISC);
- pfring_net2 = pfring_open(dev2, 1518, PF_RING_PROMISC);
- if(pfring_net1 == NULL || pfring_net2 == NULL) {
- goto end;
- }
- if (pfring_set_bpf_filter(pfring_net1, "arp or tcp or udp")) {
- goto end;
- }
- if (pfring_set_direction(pfring_net1, rx_only_direction) ||
- pfring_set_direction(pfring_net2, rx_only_direction)) {
- goto end;
- }
- if (pfring_enable_ring(pfring_net1) ||
- pfring_enable_ring(pfring_net2)) {
- goto end;
- }
- while(1) {
- unsigned char *pkt;
- struct pfring_pkthdr ring_hdr;
- if(pfring_recv(pfring_net1, &pkt, 0, &ring_hdr, 0)) {
- pfring_send(pfring_net2, pkt, ring_hdr.caplen, 1);
- }
- if(pfring_recv(pfring_net2, &pkt, 0, &ring_hdr, 0)) {
- pfring_send(pfring_net1, pkt, ring_hdr.caplen, 1);
- }
- }
- end:
- if (pfring_net1) {
- pfring_close(pfring_net1);
- }
- if (pfring_net2) {
- pfring_close(pfring_net2);
- }
- return 0;
- }
将其编译一下:
gcc test.c -o test -lpcap -lpfring -lrt
注意以上的libpcap不是apt-get的那个,而是PF_RING软件包中userland目录下的那个libpcap,总之本文不是PF_RING的文档,所以不深入这些细节,另外要感谢的是CSDN资源频道上传《PF_RING用户指南.V5.4.4.pdf》的那位,一册在手别无所求,关键是其资源分是0分!编译好的程序执行./test -i eth0 -o eth1,然后在iMac上打开网页吧,完全OK,然后ping一下百度呢?不行!为什么?因为这一句:
- pfring_set_bpf_filter(pfring_net1, "arp or tcp or udp")
只允许arp,tcp,udp通过,icmp不在此列,因此不让通过。
按照上面的代码,你只需要稍作修改,就能把你的机器打造成一个透明设备了,但是要记住,你使用的是PF_RING技术,为何要强调这一点呢?因为就效果来
看,使用libpcap/PACKET套接字和Tilera的NetLib同样能做到,但是PF_RING与众不同,和PACKET套接字相
比,PF_RING的原理让其性能更高,和NetLib相比,PF_RING更加通过,接口设计得更加好。要想学习PF_RING,下载最新版本,解压编
译吧,里面内容很丰富,有定制的Driver,有用户态的库,有内核模块,更要强调的是有超级多的例子,另外每一个README都值得一读。
现代千兆/万兆以太网卡-以Intel 82599为例
如果说NetLib是Tilera架构专有的解决方案,那么PF_RING就是x86架构上对应的通用解决方案。对于Intel 82599万兆卡,提供了很多新的机制以及对于老机制有了很多新的扩展,所有新老机制中,最让人激动的莫过于多队列的细化,如下图所示:
那如果说这个多队列和PF_RING相结合,则会变成下面这幅图所表现的:
如
此的看这幅图,内核协议栈好像有点多余,确实是。如果我们不用PF_RING机制,Linux内核协议栈如何来应对相互之间独立的RX
Queues呢?如果所有这些Queue由不同的CPU核心处理,那么能否提升性能呢?答案当然是肯定的,但是,别忘了主角是协议栈,传统的Linux内
核协议栈会使用大量的全局链表,比如skb,路由cache,socket,只要操作这些数据结构,就要lock,除了互斥的开销之外,内核协议栈本质上
是串行处理的,即在处理MAC的时候,无法预处理IP层,或者说在处理IP层的时候,无法预处理TCP,这就导致了Linux内核面对如此的Intel
82599网卡时的无所适从,即便你拥有超过60个CPU核心,在Linux内核看来也是没有什么用武之地,实际上虽然我没有实测过,我觉得
Windows也一样的结果,甚至厂商优化过的UNIX也好不到哪去。
要想将庞大的串行内核协议栈切分成多个以解放lock开销,方法就是切分协议栈本身,对于Linux而言,轻量级的方法就是划分多个Net
Namespace,每一个ns都有一套网络协议栈数据结构,不同的ns之间网络操作无需互斥。但令人倍感不爽的是,Linux的ns不能切割Intel
82599的Multi RX
Queue,唉...对于只有一块82599卡的设备来讲,它只能属于一个ns,这事实上也就是堵住了这条路!但是好在Linux内核和Intel驱动可
以随便改...重量级的方式就是使用虚拟机,Intel的官方驱动程序完全支持虚拟机。
这一切都太麻烦了,如果有一个用户态协议栈,直接接在PF_RING的上面,岂不更好,每一个CPU核心运行一个也未尝不可。我怎么又扯到协议栈了啊,之
前不是说要忘掉协议栈吗?是的,在一进一出的设备中,协议栈确实没有用,但是在应用服务器,只要TC/IP还在,TCP的逻辑还是要处理的,比如你就要处
理TCP状态机,处理流控,拥控等,而这一切并不是说因为Linux的协议栈在内核就一定要在内核被处理,既然数据包依次经过了网卡芯片,多队列逻
辑,PF_RING一路无损地到达了用户态,为何不直接进一步让它进入一个协议栈呢?对于应用服务器,如果你用PF_RING,有一个无锁的用户态协议栈
是比较好的。
现代猛卡的软件应对-PF_RING DNA(Derict NIC Access)
网
卡芯片,CPU,芯片组,总线的性能都在迅猛提升,那么软件呢?很遗憾,软件已经显得步履有点踉跄了。然而PF_RING为你做了更进一步的让步,那就是
连唯一的一次内存拷贝也省了,即根本不需要将数据包从网卡拷贝到mmap到用户态的内存,而是在物理层收到包的时候,直接将数据包放到用户指定的地方。具
体来讲,就是将网卡上存储器map到了地址空间的某处,虚拟内存真的是个好东西啊。
装上支持DNA网卡的驱动,载入DNA用户库和NDA应用,网卡将不再是一块网卡,而纯粹变成了从远端延伸到了本机的内存。
基于PF_RING的VPN设备
我
可以回答那个关于VPN的问题了,如果使用PF_RING,我会将数据包抓取到用户态的进程,取出其MAC头,IP头备用,加密整个IP数据报,然后用取
出的IP头,MAC头重新封装加密后的数据(传输模式),或者用新的IP头,备用的MAC封装数据(隧道模式),甚至可以直接加密TCP/UDP的载荷而
保留TCP/UDP的头,这样的话,我的VPN设备真的连一个IP地址都不用配置却能实现超级灵活的加解密措施,真的成了一根昂贵的网线。
现在时间2014年6月22日1点11分,阿根廷和伊朗依然1:1,伊朗好危险的一次进攻...离小小生日还有5天时间,离我噩梦的结束还有不到4天时间。