BPF(Berkeley Packet Filter) 使用

BPF可以将filter放到kernel space,这样可以减少user space的cpu负载。使用该功能需要包含头文件#include <linux/filter.h>

BSP指令使用类似汇编的伪代码,并运行在一个简单的虚拟机上。


创建filter 指令

BSP的指令写起来比较复杂,好在我们可以使用tcpdump -dd的命令来生成这样的指令。 关于tcpdump中的过滤命令可以参考1

例如,下面的命令是用来在抓取802.11帧的时候的过滤条件:
1. nods或者tods的包;
2. 并且包的类型为部分data包和部分mgt包,主要都是由设备发往AP的。
tcpdump -i wlan0-monitor -e -d \(dir nods or dir tods\) and \(\(type data subtype data or subtype data-cf-ack or subtype data-cf-poll or subtype data-cf-ack-poll or subtype null or subtype cf-ack or subtype cf-poll or subtype cf-ack-poll\) or \(type mgt subtype assoc-req or subtype reassoc-req or subtype probe-req or subtype atim or subtype disassoc or subtype auth or subtype deauth\)\)
使用上述的tcpdump -d命令后得到的可读的BPF指令为:
具体每个指令的含义可以参考2
(000) ldb      [3]
(001) lsh      #8
(002) tax
(003) ldb      [2]
(004) or       x
(005) tax
(006) ldb      [x + 1]
(007) jset     #0x3             jt 8    jf 10
(008) and      #0x3
(009) jeq      #0x1             jt 10   jf 51
(010) ldb      [x + 0]
(011) and      #0xfc
(012) jeq      #0x8             jt 52   jf 13
(013) ldb      [x + 0]
(014) and      #0xfc
(015) jeq      #0x18            jt 52   jf 16
(016) ldb      [x + 0]
(017) and      #0xfc
(018) jeq      #0x28            jt 52   jf 19
(019) ldb      [x + 0]
(020) and      #0xfc
(021) jeq      #0x38            jt 52   jf 22
(022) ldb      [x + 0]
(023) and      #0xfc
(024) jeq      #0x48            jt 52   jf 25
(025) ldb      [x + 0]
(026) and      #0xfc
(027) jeq      #0x58            jt 52   jf 28
(028) ldb      [x + 0]
(029) and      #0xfc
(030) jeq      #0x68            jt 52   jf 31
(031) ldb      [x + 0]
(032) and      #0xfc
(033) jeq      #0x78            jt 52   jf 34
(034) ldb      [x + 0]
(035) jset     #0xfc            jt 36   jf 52
(036) and      #0xfc
(037) jeq      #0x20            jt 52   jf 38
(038) ldb      [x + 0]
(039) and      #0xfc
(040) jeq      #0x40            jt 52   jf 41
(041) ldb      [x + 0]
(042) and      #0xfc
(043) jeq      #0x90            jt 52   jf 44
(044) ldb      [x + 0]
(045) and      #0xfc
(046) jeq      #0xa0            jt 52   jf 47
(047) ldb      [x + 0]
(048) and      #0xfc
(049) jeq      #0xb0            jt 52   jf 50
(050) jeq      #0xc0            jt 52   jf 51
(051) ret      #0
(052) ret      #65535

使用tcpdump -dd命令可以得到C语言中使用的指令,如下:

{ 0x30, 0, 0, 0x00000003 },
{ 0x64, 0, 0, 0x00000008 },
{ 0x7, 0, 0, 0x00000000 },
{ 0x30, 0, 0, 0x00000002 },
{ 0x4c, 0, 0, 0x00000000 },
{ 0x7, 0, 0, 0x00000000 },
{ 0x50, 0, 0, 0x00000001 },
{ 0x45, 0, 2, 0x00000003 },
{ 0x54, 0, 0, 0x00000003 },
{ 0x15, 0, 41, 0x00000001 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 39, 0, 0x00000008 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 36, 0, 0x00000018 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 33, 0, 0x00000028 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 30, 0, 0x00000038 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 27, 0, 0x00000048 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 24, 0, 0x00000058 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 21, 0, 0x00000068 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 18, 0, 0x00000078 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x45, 0, 16, 0x000000fc },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 14, 0, 0x00000020 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 11, 0, 0x00000040 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 8, 0, 0x00000090 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 5, 0, 0x000000a0 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 2, 0, 0x000000b0 },
{ 0x15, 1, 0, 0x000000c0 },
{ 0x6, 0, 0, 0x00000000 },
{ 0x6, 0, 0, 0x0000ffff }

将filter attach到socket上

static struct sock_filter BPF_code[] = {
    { 0x30, 0, 0, 0x00000003 },
    { 0x64, 0, 0, 0x00000008 },
    { 0x7, 0, 0, 0x00000000 },
    { 0x30, 0, 0, 0x00000002 },
    { 0x4c, 0, 0, 0x00000000 },
    { 0x7, 0, 0, 0x00000000 },
    { 0x50, 0, 0, 0x00000001 },
    { 0x45, 0, 2, 0x00000003 },
    { 0x54, 0, 0, 0x00000003 },
    { 0x15, 0, 41, 0x00000001 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 39, 0, 0x00000008 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 36, 0, 0x00000018 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 33, 0, 0x00000028 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 30, 0, 0x00000038 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 27, 0, 0x00000048 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 24, 0, 0x00000058 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 21, 0, 0x00000068 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 18, 0, 0x00000078 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x45, 0, 16, 0x000000fc },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 14, 0, 0x00000020 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 11, 0, 0x00000040 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 8, 0, 0x00000090 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 5, 0, 0x000000a0 },
    { 0x50, 0, 0, 0x00000000 },
    { 0x54, 0, 0, 0x000000fc },
    { 0x15, 2, 0, 0x000000b0 },
    { 0x15, 1, 0, 0x000000c0 },
    { 0x6, 0, 0, 0x00000000 },
    { 0x6, 0, 0, 0x0000ffff }
};

    struct sock_fprog kernel_filter;

    /* create kernel space filter */
    kernel_filter.len = sizeof(BPF_code) / sizeof(BPF_code[0]);
    kernel_filter.filter = BPF_code;

    ret = setsockopt(s_capture_sock, SOL_SOCKET, SO_ATTACH_FILTER, &kernel_filter,
            sizeof(kernel_filter));
    if (ret == -1) {
        syslog(LOG_ERR, "(%s): attacth filter to capture socket failed, %d", __func__, errno);
        reset_kernel_filter();
    }
static int
reset_kernel_filter()
{
    /*
     * setsockopt() barfs unless it get a dummy parameter.
     * valgrind whines unless the value is initialized,
     * as it has no idea that setsockopt() ignores its
     * parameter.
     */
    int dummy = 0;

    return setsockopt(s_capture_sock, SOL_SOCKET, SO_DETACH_FILTER,
                   &dummy, sizeof(dummy));
}

这样就可以在kernel space中开启过滤功能了。

使用了BPF之后,发现user space的用户进程cpu负载明显降低

参考文献:

【1】http://www.tcpdump.org/manpages/pcap-filter.7.html
【2】https://www.kernel.org/doc/Documentation/networking/filter.txt