关于PF_RING/Intel82599/透明VPN的一些事
事情的起因是这样的。
一共有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的网站介绍,有以下图示所示区别:
PF_RING的背后
很多人都只是认为PF_RING只是一个高性能的抓包机制,提供本机的数据包镜像分析,实现网络审计,这只是按照传统的思路来解释的。更进一步,PF_RING机制颠覆了网络中间节点解释数据包的方式。按照传统的观念,中间网络节点只能按照协议栈的层次一层一层地解析数据包,所谓路由器是三层设备,交换机是二层设备,防火墙分通ky"http://www.it165.net/qq/" target="_blank" class="keylink">qq2/rLjt8C78Me9us3I/bLjt8C78Me9Li4uyrnTw1BGX1JJTke1xMnosbijrMv8v8nS1L2ryv2+3bD81rG907TTzfi/qLXE0L7GrERNQbW9xOO7+sb3yc+1xMTatOajrL32tMu2+NLRo6zIu7rzxOPNqLn90ru49tOm08OzzNDytviyu8rHxNq6y9Ct0unVu8C0tKbA7cr9vt2w/KOs1sHT2su1xOO1xNOm08OzzNDy1PXDtLSm1sPK/b7dsPyjrM7SwLTB0L7ZvLi49qO6PGJyIC8+PHN0cm9uZz4xLsnutsi94s72yv2+3bD8o6ywtNXVuPfW1sTjv8nS1M/rtb21xMGjtsjAtL3izva74buwo6zIu7rzvMfCvMnzvMbQxc+io7s8YnIgLz4yLszhuam439DUxNy1xMjrx9a87LLiuabE3KO7PGJyIC8+My7Xqreiyv2+3bD8o6ywtNXVwrfTycb3tcS3vcq9oaO1q8rHsrvU2b32vfbNqLn9sunRr8K308mx7bXEt73Kvb340NBJUMK308mjrLb4yse/ydLUzai5/bj31ta499H5tcS3vcq9o6zXqreise3N6sir08nE49fUvLq2qNLlo6yxyMjnyrXP1tK7uPbNqNPDtcRTRE7B97Hto7s8YnIgLz40Lrj5vt3Jz8PmtdoyteO1xLqs0uWjrMTjv8nS1L72tqjExNCpsPyxu7aqxvqjrNXivs3Kx9K7uPa439DUxNy1xLfAu/DHvaGjPC9zdHJvbmc+PGJyIC8+z+CxyNCt0unVu7XEtK7Q0L3ivva3vbC4o6zKudPDUEZfUklOR8rH0ru49rj8vNO439CntcS3vbC4o6yyu7WruN/Qp6OstvjH0sHpu+6ho8jnufvE49O109C24LrL0MS1xLSmwO3G96OsxOPJ9dbBv8nS1L/J0tTU2tPDu6fMrLKi0NC0psDtyv2+3bD8tcS497j2suPQxc+io6zI58/CzbzL+cq+o7o8YnIgLz48cD48YnIgLz48L3A+PHA+PGltZyBzcmM9"http://www.it165.net/uploadfile/files/2014/0623/20140623173511846.jpg" alt="" />如果只有一台电脑,这个拓扑还真不好搭建,别告诉我说使用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-eth3: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 eth3,然后在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天时间。