一种通过PF_RING提高Snort效率的方法

by LauCyun May 8,2018 09:35:45 23,925 views

上周,公司的IPS产品在公安三所检测,检测出丢包很严重,如图1所示:


图1 测试结果

从图1可以看出,丢包率在50%以上,确实丢包很严重。

0x01 侦察

公司的IPS产品是基于Snort二次开发的。

经过分析,Snort是使用libpcap库作为数据包的捕获工具,通过调用libpcap库的函数从网卡获取数据包。它的工作流程(如图2所示):

  • 如果命令行没有指定网卡设备或者文件接口时,Snort先调用pcap_lookupdev函数获取网络接口,然后调用pcap_open_live函数得到该接口的描述符,再调用pcap_lookupnet函数获取网络接口的IP和子网掩码;
  • 接着调用pcap_compile函数编译过滤规则字符串;
  • 最后调用pcap_setfilter和pcap_freecode设置数据包过滤器,并且释放bpf_program结构体。

这时Snort已经打开了libpcap接口的句柄,接着调用pacp_loop循环获取数据包,同时调用processpacket函数理数据包。


图2 Snort的工作流程

Snort是开源的软件,并且性能很好,但是将Snort其应用在大规模、大流量的网络中,Snort的效率就非常低下,如图1出现丢包严重的问题。几番调研后,可以使用PF_RING替代libpcap。

PF_RING是一个第三方的内核数 据包捕获接口,类似于libpcap。 它提供一种PF_RING类型的套接字,大大增加了数据包捕获的 效率 。

0x02 PF_RING

PF_RING技术是德国人@Luca Deri发明的一种高效数据包捕获技术。简单来说PF_RING是一个高速数据包捕获库,通过它可以实现将通用计算机变成一个有效且便宜的网络测量工具箱,进行数据包和现网流量的分析和操作。同时支持调用用户级别的API来创建更有效的应用程序。

1 基本原理

PF_RING基本原理:PF_RING将网卡接收的数据包存储在一个环状缓存,这个环状缓存有两个接口,一个供网卡向其中写数据包,另一个为应用层程序提供读取数据包的接口,读取数据包的接口通过mmap实现。网卡的中断模式采用Device Polling,Linux下也称作NAPI,在网卡驱动程序中支持。而这种环状缓存是通过向内核中添加一种新的协议簇,添加一种新的带缓存的socket来实现的,提供通用socket接口。如图3所示。


图3 PF_RING架构图

2 提高效率的核心技术

PF_RING使用了两个技术来提高包的捕获效率:

  • NAPI技术减少CPU中断响应网卡的次数。
    • NAPI的基本思想是:当数据包到来时网卡发出中断请求,CPU响应中断请求进入中断处理程序,同时CPU要关闭中断处理请求。在中断处理程序中,CPU采用轮询的方式获取数据包。当网卡接收了一定数量的数据包以后,就退出中断服务处理程序并且开中断,响应中断请求。这种方式会大大减少CPU的中断次数,在一次响应中断中处理多个数据包,对系统性能是极大的改善。 
  • 零拷贝技术减少数据在内存之间的拷贝。
    • 它的基本思想是数据从设备拷入内存时,数据直接从内核态内存传入用户态内存,从而避免了系统调用和内存拷贝,整个过程不需要CPU的参与,这就降低了CPU的负载。“零拷贝”可以通过MMAP(内存映射)和DMA(直接内存存取)来实现。数据由DMA从网卡直接传输到内核空间,然后应用程序的用户态内存可以通过MMAP直接访问内核数据,这样就消除了数据在内核态和用户态之间的拷贝。 

简单的介绍就到这,详细的资料可以Google或者阅读:

0x03 安装PF_RING

虽然网上有很多关于PF_RING安装的教程,比如:Centos6.8 64位编译安装PF_RING心得和总结 - CSDN博客,以及官方文档:https://www.ntop.org/guides/pf_ring

按照文档的步骤,却出现错误:insmod: error inserting 'pf_ring.ko': -1 Unknown symbol in module,网上有人说是“没有卸载当前的网卡驱动,新的加载不了,需要卸载网卡驱动”,但是我把网卡卸载驱动后,依然加载不上,顿时就cryingcryingcrying

通过DuckDuckGo经过一番调研后,发现https://centos.pkgs.org/6/forensics-x86_64/pfring-7.0.0-1887.x86_64.rpm.html有现成的rpm,可以直接yum安装,看到这个瞬间就laughlaughlaugh

1 安装rpm包

首先,需要安装2个rpm包:epelcert-forensics-tools-release-el6

epelhttps://mirrors.aliyun.com/epel

## CentOS 6 and Red Hat (RHEL) 6 ##
rpm -Uvh https://mirrors.aliyun.com/epel/epel-release-latest-6.noarch.rpm

## CentOS 7 and Red Hat (RHEL) 7 ##
rpm -Uvh https://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm

cert-forensics-tools-release-el6https://centos.pkgs.org/

## CentOS 6 and Red Hat (RHEL) 6 ##
rpm -Uvh https://forensics.cert.org/cert-forensics-tools-release-el6.rpm

## CentOS 7 and Red Hat (RHEL) 7 ##
rpm -Uvh https://forensics.cert.org/cert-forensics-tools-release-el7.rpm

2 安装pfring

然后,就是安装pfring,直接运行如下命令:

yum --enablerepo=forensics install pfring


图4 安装pfring

如上图4,它会安装pfringdkmselfutils-libelf-develkernel-debug-devel4个软件包,安装完成后生成如下文件:

  • /etc/init.d/cluster
  • /etc/init.d/pf_ring
  • /etc/ld.so.conf.d/pf_ring.conf
  • /usr/bin/n2if
  • /usr/bin/pfcount
  • /usr/bin/pfsend
  • /usr/bin/zbalance_ipc
  • /usr/bin/zcount
  • /usr/bin/zcount_ipc
  • /usr/bin/zsend
  • /usr/lib64/wireshark/extcap/ntopdump
  • /usr/local/bin/clusterctl
  • /usr/local/bin/pf_ringctl
  • /usr/local/include/nbpf.h
  • /usr/local/include/pfring.h
  • /usr/local/include/pfring_zc.h
  • /usr/local/include/linux/pf_ring.h
  • /usr/local/lib/libpcap.a
  • /usr/local/lib/libpcap.so.1.8.1
  • /usr/local/lib/libpfring.a
  • /usr/local/lib/libpfring.so
  • /usr/local/lib/libsfbpf.so.0
  • /usr/local/lib/libsfbpf.so.0.0.1
  • /usr/local/lib/daq/daq_pfring.la
  • /usr/local/lib/daq/daq_pfring.so
  • /usr/local/lib/daq/daq_pfring_zc.la
  • /usr/local/lib/daq/daq_pfring_zc.so
  • /usr/local/pfring/README-DAQ.1st
  • /usr/local/pfring/README.FIRST

现在启动pfring,如图5所示:


图5 启动pfring

但是,启动失败,那是由于未安装pfring-dkms

3 安装pfring-dkms

接着安装pfring-dkms,直接运行如下命令:

yum install pfring-dkms

0x04 启动PF_RING

直接运行如下命令:

pf_ringctl start

又双叒叕出错了,如图6所示:


图6 pfring启动失败

出现这个错误的原因是:安装的kernel-devel的版本跟系统的内核版本不一致。

从图6得知,目前系统的内核版本是2.6.32-431.el6.x86_64,从图4得知安装的kernel-devel版本是2.6.32-696.23.1.el6.x86_64,从而导致/lib/modules/2.6.32-431.el6.x86_64中的build链接指向的../../../usr/src/kernels/2.6.32-431.el6.x86_64/路径不存在。解决方法如下:

# 删除build链接
rm -rf /lib/modules/2.6.32-431.el6.x86_64/build
# 重新创建链接
ln -s /usr/src/kernels/2.6.32-696.23.1.el6.x86_64/ /lib/modules/2.6.32-431.el6.x86_64/build  

再启动pfring,此时就不会报上述问题了。

查看pf_ring是否加载成功,如图7所示:


图7 已载入系统的模块

如图7所示即为pf_ring加载成功。

顺便查看一下驱动pf_ring的信息,如图8所示:


图8 驱动pf_ring的信息

Ok,pf_ring经载入了系统,接下来就是测试Snort使用它的效果啦~~~

0x05 测试

1 测试环境

测试环境:

  • 客户端:
    • 系统:CentOS 6.5
    • IP地址:192.168.0.90
    • 软件:iperf3
  • 服务端:
    • 系统:CentOS 6.5
    • IP地址:192.168.0.89
    • 软件:iperf3
  • 网线:
    • 六类网线2根
  • IPS产品一台
    • 工作网卡:eth2、eth3

其测试网络结构图,如图9所示:


图9 测试网络结构图

客户端和服务端是串联在IPS之间。

2 测试

在官方文档3. Using Snort with PF_RING — PF_RING 7.0 documentation中已介绍,以IPS模式启动Snort的命令如下:

snort --daq-dir=/usr/local/lib/daq --daq pfring  -i ethX:ethY -e -Q

可以用,列出多对网卡,比如:-i ethX:ethY,ethM:ethN

在测试之前,先创建以下文件夹:

# PID
mkdir -p /var/run/snort/{p1,p2,p3,p4}
# alert
mkdir -p /var/log/snort/{instance-1,instance-2,instance-3,instance-4}

接着,运行如下命令,检测Snort是否可以启动:

snort -c /etc/snort/snort.conf --daq-dir=/usr/local/lib/daq --daq pfring --daq-mode inline -i eth2:eth3 --daq-var fast-tx=1 --daq-var clusterid=10,11 --daq-var bindcpu=1

又双叒叕出错了,如图10所示:


图10 snort启动失败

出错的原因是:缺少libhiredis.so.0.10库文件。只需要安装hiredis即可解决,解决方法如下:

# 下载
git clone  https://github.com/redis/hiredis
cd hiredis/
# 编译
make
# 安装
make install

hiredis安装完成后,其libhiredis.so库文件版本是libhiredis.so.0.13,而pf_ring依赖的版本是libhiredis.so.0.10,需要运行如下命令创建软连接:

ln -s /usr/local/lib/libhiredis.so.0.13 /usr/local/lib/libhiredis.so.0.10

重新启动Snort,如图11所示:


图11 运行snort

Nice,Snort启动没问题了,接下来就是测试多核情况下Snort的运行效率。

启动4个进程,且每个进程绑定1核CPU,命令如下:

$ snort -q --pid-path /var/run/snort/p1 --create-pidfile -c /etc/snort/snort.conf -l /var/log/snort/instance-1 --daq-dir=/usr/local/lib/daq --daq pfring --daq-mode inline -i eth2:eth3 --daq-var fast-tx=1 --daq-var clusterid=10,11 --daq-var bindcpu=1 -Q -D
$ snort -q --pid-path /var/run/snort/p2 --create-pidfile -c /etc/snort/snort.conf -l /var/log/snort/instance-2 --daq-dir=/usr/local/lib/daq --daq pfring --daq-mode inline -i eth2:eth3 --daq-var fast-tx=1 --daq-var clusterid=10,11 --daq-var bindcpu=2 -Q -D
$ snort -q --pid-path /var/run/snort/p3 --create-pidfile -c /etc/snort/snort.conf -l /var/log/snort/instance-3 --daq-dir=/usr/local/lib/daq --daq pfring --daq-mode inline -i eth2:eth3 --daq-var fast-tx=1 --daq-var clusterid=10,11 --daq-var bindcpu=3 -Q -D
$ snort -q --pid-path /var/run/snort/p4 --create-pidfile -c /etc/snort/snort.conf -l /var/log/snort/instance-4 --daq-dir=/usr/local/lib/daq --daq pfring --daq-mode inline -i eth2:eth3 --daq-var fast-tx=1 --daq-var clusterid=10,11 --daq-var bindcpu=4 -Q -D

通过iperf3发送带宽为1000M的数据包,测试效果如图12所示(上面显示器是服务端,下面显示器是客户端):


图12 测试结果

从图12(上面显示器是服务端,下面显示器是客户端)可知,综合计算其通过率为95%左右。

上面Snort的启动方式肯定不是最优的方式,具体如何调优请参考3. Using Snort with PF_RING — PF_RING 7.0 documentation

0x06 参考

Tags