ToB企服应用市场:ToB评测及商务社交产业平台

标题: Linux之bpfjit(2)使用分析和mini-tcpdump实现 [打印本页]

作者: 梦见你的名字    时间: 2024-7-19 03:37
标题: Linux之bpfjit(2)使用分析和mini-tcpdump实现
Linux之bpfjit(2)使用分析和mini-tcpdump实现

  
   Author: Once Day Date: 2024年4月13日


  一位热衷于Linux学习和开辟的菜鸟,试图谱写一场冒险之旅,大概尽头只是一场白日梦…


   漫漫长路,有人对你微笑过嘛…


  全系列文章可以参考专栏:Linux基础知识_Once-Day的博客-CSDN博客。


  参考文章:
  
  

  
1. 概述

1.1 BPF(Berkeley Packet Filter)

BPF(Berkeley Packet Filter)最初设计用于数据包过滤,即网络封包的捕获与筛选。随着时间的发展,BPF已经演酿成为一个更加通用、功能强盛的技术,尤其是在Linux内核中,它已经被扩展为eBPF(extended Berkeley Packet Filter)。
根本概念:

在FastPath,目前使用的包过滤技术,重要是BPF。eBPF必要内核支持,暂时没有合适的用户空间实现机制。
BPF本质上是一堆预界说的字节码,可以模拟加减乘除、分支判定、跳转、寄存器存入和读取等操作。这些基础操作组合起来,就能实现复杂的过滤处理逻辑
比方,下面是一些BPF的算术和判定操作符界说:
  1. //(NetBSD) - sys/net/bpf.h
  2. /* alu/jmp fields */
  3. #define BPF_OP(code)        ((code) & 0xf0)
  4. #define                BPF_ADD                0x00
  5. #define                BPF_SUB                0x10
  6. #define                BPF_MUL                0x20
  7. #define                BPF_DIV                0x30
  8. #define                BPF_OR                0x40
  9. #define                BPF_AND                0x50
  10. #define                BPF_LSH                0x60
  11. #define                BPF_RSH                0x70
  12. #define                BPF_NEG                0x80
  13. #define                BPF_MOD                0x90
  14. #define                BPF_XOR                0xa0
  15. ......
复制代码
1.2 BPFJIT(Berkeley Packet Filter Just In Time)

BPFJIT 是 JIT 编译技术在 BPF(Berkeley Packet Filter)上的应用。BPF最初是为了高效的数据包过滤而设计的,它允许在用户空间编写过滤规则,然后在内核空间实行,大幅提升了网络数据包处理的效率。
BPFJIT 则是进一步优化了这个过程,将 BPF 字节码即时编译成机器码,以便内核可以直接实行,这样可以进一步进步过滤效率。
BPF工具可以天生一个包含BPF指令码的字节序列,但是这个字节序列的实行可以有多种情势,如下:
  1. // 一个典型的(tcpdump tcp)命令生成的bpf_filter字节码,用于过滤TCP报文
  2. // 该字节码判断IPv4和IPv6协议类型,针对IPv6还考虑了分片情况处理。
  3. // 如果报文符合条件,返回8192。如果报文不符合条件,则返回0
  4. (000) ldh      [12]
  5. (001) jeq      #0x86dd          jt 2    jf 8
  6. (002) ldb      [20]
  7. (003) jeq      #0x6             jt 12   jf 4
  8. (004) ldb      [20]
  9. (005) jeq      #0x2c            jt 6    jf 8
  10. (006) ldb      [54]
  11. (007) jeq      #0x6             jt 12   jf 8
  12. (008) ldh      [12]
  13. (009) jeq      #0x800           jt 10   jf 13
  14. (010) ldb      [23]
  15. (011) jeq      #0x6             jt 12   jf 13
  16. (012) ret      #8192
  17. (013) ret      #0
复制代码
对于上述BPF指令,BSD内核代码通过一个C函数直接迭代解析,在数据报mbuf原地上进行处理。
  1. //(NetBSD) - sys/net/bpf_filter.h
  2. u_int
  3. bpf_filter(const struct bpf_insn *pc, const u_char *p, u_int wirelen,
  4.     u_int buflen)
  5. #endif
  6. {
  7.         uint32_t A, X, k;
  8. #ifndef _KERNEL
  9.         uint32_t mem[BPF_MEMWORDS];
  10.         bpf_args_t args_store = {
  11.                 .pkt = p,
  12.                 .wirelen = wirelen,
  13.                 .buflen = buflen,
  14.                 .mem = mem,
  15.                 .arg = NULL
  16.         };
  17.         bpf_args_t * const args = &args_store;
  18. #else
  19.         const uint8_t * const p = args->pkt;
  20. #endif
  21.         if (pc == 0) {
  22.                 /*
  23.                  * No filter means accept all.
  24.                  */
  25.                 return (u_int)-1;
  26.         }
  27.         /*
  28.          * Note: safe to leave memwords uninitialised, as the validation
  29.          * step ensures that it will not be read, if it was not written.
  30.          */
  31.         A = 0;
  32.         X = 0;
  33.         --pc;
  34.         for (;;) {
  35.                 ++pc;
  36.                 switch (pc->code) {
  37.                 default:
  38. #ifdef _KERNEL
  39.                         return 0;
  40. #else
  41.                         abort();
  42.                         /*NOTREACHED*/
  43. #endif
  44.                 case BPF_RET|BPF_K:
  45.                         return (u_int)pc->k;
  46.                 case BPF_RET|BPF_A:
  47.                         return (u_int)A;
  48.                 case BPF_LD|BPF_W|BPF_ABS:
  49.                         k = pc->k;
  50.                         if (k > args->buflen ||
  51.                             sizeof(int32_t) > args->buflen - k) {
  52. #ifdef _KERNEL
  53.                                 int merr;
  54.                                 if (args->buflen != 0)
  55.                                         return 0;
  56.                                 A = xword(args->pkt, k, &merr);
  57.                                 if (merr != 0)
  58.                                         return 0;
  59.                                 continue;
  60. #else
  61.                                 return 0;
  62. #endif
  63.                         }
  64.                         A = EXTRACT_LONG(&p[k]);
  65.                         continue;
  66.                 case BPF_LD|BPF_H|BPF_ABS:
  67. //...(省略大量代码)...
复制代码
FastPath的报文过滤的BPF指令码实行函数,采用就是该函数的实现方式
除了这种C函数直接循环迭代解析之外,还可以通过JIT(即时编译)技术增加处理效率。
在NetBSD实现里面,采用SLJIT技术,将BPF指令码一一对应转换为SLJIT指令码,在即时编译后,天生机器特定汇编代码,最终就可以采用C函数指针直接调用实行。
  1. //(NetBSD) - sys/net/bpfjit.c - generate_insn_code
  2. // ...(省略大量代码)...
  3. case BPF_LD:
  4.     /* BPF_LD+BPF_IMM          A <- k */
  5.     if (pc->code == (BPF_LD|BPF_IMM)) {
  6.         status = sljit_emit_op1(compiler,
  7.             SLJIT_MOV,
  8.             BJ_AREG, 0,
  9.             SLJIT_IMM, (uint32_t)pc->k);
  10.         if (status != SLJIT_SUCCESS)
  11.             goto fail;
  12.         continue;
  13.     }
  14.     /* BPF_LD+BPF_MEM          A <- M[k] */
  15.     if (pc->code == (BPF_LD|BPF_MEM)) {
  16.         if ((uint32_t)pc->k >= memwords)
  17.             goto fail;
  18.         status = emit_memload(compiler,
  19.             BJ_AREG, pc->k, extwords);
  20.         if (status != SLJIT_SUCCESS)
  21.             goto fail;
  22.         continue;
  23.     }
  24.     /* BPF_LD+BPF_W+BPF_LEN    A <- len */
  25.     if (pc->code == (BPF_LD|BPF_W|BPF_LEN)) {
  26.         status = sljit_emit_op1(compiler,
  27.             SLJIT_MOV, /* size_t source */
  28.             BJ_AREG, 0,
  29.             SLJIT_MEM1(BJ_ARGS),
  30.             offsetof(struct bpf_args, wirelen));
  31.         if (status != SLJIT_SUCCESS)
  32.             goto fail;
  33.         continue;
  34.     }
  35.     mode = BPF_MODE(pc->code);
  36.     if (mode != BPF_ABS && mode != BPF_IND)
  37.         goto fail;
  38.     if (unconditional_ret)
  39.         continue;
  40.     status = emit_pkt_read(compiler, hints, pc,
  41.         to_mchain_jump, &ret0, &ret0_size, &ret0_maxsize);
  42.     if (status != SLJIT_SUCCESS)
  43.         goto fail;
  44.     continue;
  45. // ...(省略大量代码)...
复制代码
SLJIT即时编译后通常返回一个函数,BPFJIT固定了这个函数的情势,如下:
  1. /*
  2. * Return value of a function generated by sljit have sljit_uw type
  3. * which can have a greater width. In such cases, we rely on the fact
  4. * that calling conventions use same registers for smaller types.
  5. * SLJIT_MOV_UI is passed to sljit_emit_return() to make sure that the
  6. * return value is truncated to unsigned int.
  7. */
  8. typedef unsigned int (*bpfjit_func_t)(const bpf_ctx_t *, bpf_args_t *);
复制代码
通过这个函数,BPF可以实行更多复杂的操作,同时分身效率。
1.3 SLJIT(Simple Just-In-Time)

SLJIT 是一个独立的、通用的 JIT 编译库,它不特定于任何范畴,可以被用于任何必要 JIT 功能的场所。SLJIT 的设计哲学是简单和通用,它提供了一套低层次的 API,使得开辟者可以根据自己的需求天生机器码。比如,SLJIT 可以用于实现正则表达式的快速匹配,也可以用于脚本语言的即时编译。
SLJIT和下面的JIT技术属于同类工具:

SLJIT架构支持CPU架构指令情况如下所示:
  1. Intel-x86 32
  2. AMD-x86 64
  3. ARM 32 (ARM-v5, ARM-v7 and Thumb2 instruction sets)
  4. ARM 64
  5. PowerPC 32
  6. PowerPC 64
  7. MIPS 32 (III, R1)
  8. MIPS 64 (III, R1)
  9. RISC-V 32
  10. RISC-V 64
  11. s390x (64)
  12. loogarch         #目前看到loogarch支持代码提交记录
复制代码
SLJIT使用方式雷同于汇编编程,通过中间层转换,可以屏蔽复杂的处理逻辑,下面是一个原始编程的例子:
  1. typedef sljit_sw (*func3_t)(sljit_sw a, sljit_sw b, sljit_sw c);
  2. static int branch(sljit_sw a, sljit_sw b, sljit_sw c)
  3. {
  4.     void    *code;
  5.     sljit_uw len;
  6.     func3_t  func;
  7.     struct sljit_jump *ret_c;
  8.     struct sljit_jump *out;
  9.     /* Create a SLJIT compiler */
  10.     struct sljit_compiler *C = sljit_create_compiler(NULL);
  11.     /* 3 arg, 1 temp reg, 3 save reg */
  12.     sljit_emit_enter(C, 0, SLJIT_ARG1(SW) | SLJIT_ARG2(SW) | SLJIT_ARG3(SW), 1, 3, 0, 0, 0);
  13.     /* R0 = a & 1, S0 is argument a */
  14.     sljit_emit_op2(C, SLJIT_AND, SLJIT_R0, 0, SLJIT_S0, 0, SLJIT_IMM, 1);
  15.     /* if R0 == 0 then jump to ret_c, where is ret_c? we assign it later */
  16.     ret_c = sljit_emit_cmp(C, SLJIT_EQUAL, SLJIT_R0, 0, SLJIT_IMM, 0);
  17.     /* R0 = b, S1 is argument b */
  18.     sljit_emit_op1(C, SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_S1, 0);
  19.     /* jump to out */
  20.     out = sljit_emit_jump(C, SLJIT_JUMP);
  21.     /* here is the 'ret_c' should jump, we emit a label and set it to ret_c */
  22.     sljit_set_label(ret_c, sljit_emit_label(C));
  23.     /* R0 = c, S2 is argument c */
  24.     sljit_emit_op1(C, SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_S2, 0);
  25.     /* here is the 'out' should jump */
  26.     sljit_set_label(out, sljit_emit_label(C));
  27.     /* end of function */
  28.     sljit_emit_return(C, SLJIT_MOV, SLJIT_RETURN_REG, 0);
  29.     /* Generate machine code */
  30.     code = sljit_generate_code(C);
  31.     len  = sljit_get_generated_code_size(C);
  32.     /* Execute code */
  33.     func = (func3_t)code;
  34.     printf("func return %ld\n", func(a, b, c));
  35.     dump_code(code, len);
  36.     /* Clean up */
  37.     sljit_free_compiler(C);
  38.     sljit_free_code(code);
  39.     return 0;
  40. }
复制代码
这段SLJIT处理天生了一个简单的函数,C语言等价表示如下:
  1. sljit_sw func(sljit_sw a, sljit_sw b, sljit_sw c)
  2. {
  3.     if ((a & 1) == 0)
  4.         return c;
  5.     return b;
  6. }
复制代码
SLJIT即时编译天生的汇编代码经过反编译后,输出如下:
  1. Disassembly of section .data:
  2. 0000000000000000 <.data>:
  3.    0:   f3 0f 1e fa                     endbr64
  4.    4:   53                              push   %rbx
  5.    5:   41 57                           push   %r15
  6.    7:   41 56                           push   %r14
  7.    9:   48 8b df                        mov    %rdi,%rbx
  8.    c:   4c 8b fe                        mov    %rsi,%r15
  9.    f:   4c 8b f2                        mov    %rdx,%r14
  10.   12:   48 89 d8                        mov    %rbx,%rax
  11.   15:   48 83 e0 01                     and    $0x1,%rax
  12.   19:   48 83 f8 00                     cmp    $0x0,%rax
  13.   1d:   74 05                           je     0x24
  14.   1f:   4c 89 f8                        mov    %r15,%rax
  15.   22:   eb 03                           jmp    0x27
  16.   24:   4c 89 f0                        mov    %r14,%rax
  17.   27:   41 5e                           pop    %r14
  18.   29:   41 5f                           pop    %r15
  19.   2b:   5b                              pop    %rbx
  20.   2c:   c3                              ret  
复制代码
这个汇编代码并不算高效,因为很多无效堆栈生存操作。不外这也是编译器优化的痛点所在,即使用GCC编译C代码,在没有高效优化模型和编程技巧下,天生的汇编指令也是非常繁复
1.4 BPF和eBPF的兼容性

eBPF(extended Berkeley Packet Filter)是BPF(Berkeley Packet Filter)的一个扩展,它们在焦点概念上是兼容的,但eBPF提供了更多的功能和更大的机动性。下面是两者之间的关系和兼容性方面的一些细节:
基础兼容性

指令集和功能

向后兼容

eBPF支持类C语言语法,相比于BPF的原始字节码,易用性大大进步,但是整个框架也更加复杂,必要Clang专门工具进行编译和开辟。
1.5 常见BPF技术区别和联系

BPFJIT一般在内核中有实现,支持三类操作:

整体处理逻辑如下:
     libpcap库支持将常见的tcpdump下令转换为BPF指令码,从而实现机动抓包功能。对于用户空间开辟的程序,也可以支持雷同的技术。在第二章,会借助bpf技术实现一个Tcpdump-mini程序,在里面实行上述三种bpf操作,并给出对比数据。
2. 简易抓包程序(Tcpdump-mini)

2.1 获取SLJIT和BPFJIT源码

SLJIT源码下载: zherczeg/sljit: Platform independent low-level JIT compiler (github.com)
BPFJIT源码下载: alnsn/bpfjit: Just-in-Time compilation of bpf (github.com)
Ubuntu下创建一个干净的目录,必要安装好GNU开辟套件(缺啥直接apt安装即可):
  1. # 例如安装mk-configure
  2. sudo apt install mk-configure
复制代码
先下载BPFJIT源码,再下载SLJIT源码:
  1. ubuntu->bpf-sop:$ git clone https://github.com/alnsn/bpfjit.git
  2. Cloning into 'bpfjit'...
  3. remote: Enumerating objects: 1092, done.
  4. remote: Total 1092 (delta 0), reused 0 (delta 0), pack-reused 1092
  5. Receiving objects: 100% (1092/1092), 215.03 KiB | 78.00 KiB/s, done.
  6. Resolving deltas: 100% (666/666), done.
  7. ubuntu->bpf-sop:$ git clone https://github.com/zherczeg/sljit.git
  8. Cloning into 'sljit'...
  9. remote: Enumerating objects: 6679, done.
  10. remote: Counting objects: 100% (6679/6679), done.
  11. remote: Compressing objects: 100% (1310/1310), done.
  12. remote: Total 6679 (delta 5411), reused 6545 (delta 5330), pack-reused 0
  13. Receiving objects: 100% (6679/6679), 3.99 MiB | 58.00 KiB/s, done.
  14. Resolving deltas: 100% (5411/5411), done.
  15. ubuntu->bpf-sop:$ ll
  16. drwxrwxr-x 9 ubuntu ubuntu 4096 Mar 28 22:45 bpfjit/
  17. drwxrwxr-x 7 ubuntu ubuntu 4096 Mar 28 22:50 sljit/
复制代码
SLJIT的版本一直在更新,但是API存在不兼容变革,因此必要先找到对应版本的SLJIT,目前是BPFJIT指定的版本可以直接用。
  1. ubuntu->bpf-sop:$ cd sljit/
  2. ubuntu->sljit:$ git checkout 8d536bf7c334f9e31a5cc366e5d5d8cd1cd431b1
  3. Note: switching to '8d536bf7c334f9e31a5cc366e5d5d8cd1cd431b1'.
  4. # 2020年的版本
  5. ubuntu->sljit:$ git log
  6. commit 8d536bf7c334f9e31a5cc366e5d5d8cd1cd431b1 (HEAD)
  7. Author: Carlo Marcelo Arenas Belón <carenas@gmail.com>
  8. Date:   Tue Aug 11 03:17:47 2020 -0700
  9.     config: detect gcc support for fastcall (#75)
  10.    
  11.     haiku x86 uses gcc 2.95.2 as the system compiler and fails to build,
  12.     because support for the fastcall calling convention was added with 3.4.
  13.    
  14.     detect the gcc version before enabling the attribute  and while at it
  15.     reverse the condition and refactor the surrrounding code.
复制代码
打包当前版本sljit源码,复制到bpfjit目录下面解压,必要留意别覆盖了原有的Makefile文件,否则会编译报错。
  1. ubuntu->sljit:$ git archive --format=tar --output=sljit.tar HEAD
  2. ubuntu->sljit:$ cd ../bpfjit/sljit/
  3. ubuntu->sljit:$ mv Makefile bpf-sljit.mk
  4. ubuntu->sljit:$ tar -xf ../../sljit/sljit.tar
  5. ubuntu->sljit:$ ll
  6. total 56
  7. drwxrwxr-x 6 ubuntu ubuntu 4096 Mar 28 23:01 ./
  8. drwxrwxr-x 9 ubuntu ubuntu 4096 Mar 28 22:45 ../
  9. -rw-rw-r-- 1 ubuntu ubuntu 5627 Aug 11  2020 API_CHANGES
  10. -rw-rw-r-- 1 ubuntu ubuntu   44 Mar 28 22:45 bpf-sljit.mk
  11. drwxrwxr-x 3 ubuntu ubuntu 4096 Aug 11  2020 doc/
  12. -rw-rw-r-- 1 ubuntu ubuntu   11 Aug 11  2020 .gitignore
  13. -rw-rw-r-- 1 ubuntu ubuntu  245 Aug 11  2020 INTERNAL_CHANGES
  14. -rw-rw-r-- 1 ubuntu ubuntu 4290 Aug 11  2020 Makefile
  15. -rw-rw-r-- 1 ubuntu ubuntu 1033 Aug 11  2020 README
  16. drwxrwxr-x 2 ubuntu ubuntu 4096 Aug 11  2020 regex_src/
  17. drwxrwxr-x 2 ubuntu ubuntu 4096 Aug 11  2020 sljit_src/
  18. drwxrwxr-x 2 ubuntu ubuntu 4096 Aug 11  2020 test_src/
复制代码
交换Makefile和bpf-sljit.mk两个文件的名字,bpfjit有自己的一套编译流程,所以必要分开编译。
  1. ubuntu->sljit:$ mv Makefile sljit-self.mk
  2. ubuntu->sljit:$ mv bpf-sljit.mk Makefile
复制代码
修改一下Makefile文件,通过Make子进程单独编译测试程序,默认SLJIT是源码分发,不编译动态库和静态库。
先编译SLJIT,并且测试一下功能:
  1. ubuntu->sljit:$ make -f sljit-self.mk
  2. mkdir -p bin
  3. cc  -DSLJIT_CONFIG_AUTO=1 -Isljit_src -O2 -Wall -c -o bin/sljitMain.o test_src/sljitMain.c
  4. cc  -DSLJIT_CONFIG_AUTO=1 -Isljit_src -O2 -Wall -c -o bin/sljitTest.o test_src/sljitTest.c
  5. cc  -DSLJIT_CONFIG_AUTO=1 -Isljit_src -O2 -Wall -c -o bin/sljitLir.o sljit_src/sljitLir.c
  6. cc -O2 -Wall  bin/sljitMain.o bin/sljitTest.o bin/sljitLir.o -o bin/sljit_test -lm -lpthread
  7. cc  -DSLJIT_CONFIG_AUTO=1 -Isljit_src -O2 -Wall -fshort-wchar -c -o bin/regexMain.o regex_src/regexMain.c
  8. cc  -DSLJIT_CONFIG_AUTO=1 -Isljit_src -O2 -Wall -fshort-wchar -c -o bin/regexJIT.o regex_src/regexJIT.c
  9. cc -O2 -Wall  bin/regexMain.o bin/regexJIT.o bin/sljitLir.o -o bin/regex_test -lm -lpthread
  10. ubuntu->sljit:$ export LD_LIBRARY_PATH=./bin
  11. ubuntu->sljit:$ ./bin/sljit_test
  12. Pass -v to enable verbose, -s to disable this hint.
  13. SLJIT tests: all tests are PASSED on x86 64bit (little endian + unaligned) (with fpu)
  14. ubuntu->sljit:$ ./bin/regex_test
  15. Pass -v to enable verbose, -s to disable this hint.
  16. REGEX tests: all tests are PASSED on x86 64bit (little endian + unaligned)
复制代码
测试完毕可以看到功能正常,然后继续编译bpfjit。必要修改以下部分代码,避免编译报错异常退出:
  1. # bpfjit/test/test_empty.c 39行 添加初始化值
  2.         struct bpf_insn dummy = {0};
复制代码
使用mkcmake直接编译,如果正常,将直接编译乐成,如果存在问题,按照编译提示修改即可(编译器版本不同,会有新增报错,这个很正常)。
  1. ubuntu->bpfjit:$ mkcmake
  2. ==================================================
  3. all ===> sljit
  4. ==================================================
  5. all ===> sljit/sljit_src
  6. ==================================================
  7. all ===> src
  8. ==================================================
  9. all ===> test
  10. ==================================================
  11. all ===> benchmark
  12. cc   -I ../src -I ../sljit/sljit_src/ -DSLJIT_CONFIG_AUTO=1       -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wswitch -Wshadow -Wcast-qual -Wwrite-strings -Wno-unused-parameter    -Werror       -c -o benchmark.o -O2 -g  benchmark.c
  13. cc   -I ../src -I ../sljit/sljit_src/ -DSLJIT_CONFIG_AUTO=1       -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wswitch -Wshadow -Wcast-qual -Wwrite-strings -Wno-unused-parameter    -Werror       -c -o c.o -O2 -g  c.c
  14. cc -o bpfjit_benchmark benchmark.o c.o     -L /home/ubuntu/NetBSD/bpf-sop/bpfjit/benchmark/../src -L /home/ubuntu/NetBSD/bpf-sop/bpfjit/benchmark/../sljit/sljit_src   -lpcap -lbpfjit -lsljit
复制代码
安装到固定目录中:
  1. ubuntu->bpfjit:$ export DESTDIR=/home/ubuntu/NetBSD/bpf-sop
  2. ubuntu->bpfjit:$ env PREFIX=/ mkcmake install
  3. ==================================================
  4. install ===> sljit
  5. ==================================================
  6. install ===> sljit/sljit_src
  7. if test -n "/home/ubuntu/NetBSD/bpf-sop//lib"; then mkc_install -c -d -m 755 /home/ubuntu/NetBSD/bpf-sop//lib; fi
  8. mkc_install  -c -o ubuntu  -g ubuntu -m 644 libsljit.a /home/ubuntu/NetBSD/bpf-sop//lib/libsljit.a
  9. mkc_install  -c -o ubuntu  -g ubuntu -m 644 libsljit_pic.a /home/ubuntu/NetBSD/bpf-sop//lib/libsljit_pic.a
  10. mkc_install  -c -o ubuntu  -g ubuntu -m 644 libsljit.so.1.0.0 /home/ubuntu/NetBSD/bpf-sop//lib/libsljit.so.1.0.0
  11. ln -s -f libsljit.so.1.0.0  /home/ubuntu/NetBSD/bpf-sop//lib/libsljit.so.1
  12. ln -s -f libsljit.so.1.0.0  /home/ubuntu/NetBSD/bpf-sop//lib/libsljit.so
  13. ==================================================
  14. ......
复制代码
可以实行一下bpfjit的单位测试程序,如下:
  1. ubuntu->bpf-sop:$ export LD_LIBRARY_PATH=./lib
  2. ubuntu->bpf-sop:$ export PATH=$PATH:./bin
  3. ubuntu->bpf-sop:$ ./bin/bpfjit_test
  4. bpfjit_test: test_copx.c:139 (in test_copx_ret_A): code(&ctx, &args) == 13
  5. bpfjit_test: test_copx_extmem.c:96 (in test_copx_ret_mem): code(&ctx, &args) == 13
  6. bpfjit_test: test_copx_extmem.c:138 (in test_copx_ret_preinited_mem): code(&ctx, &args) == 3
复制代码
这里打印三个测试信息,阐明bpfjit有三个单位测试用例无法测试通过,目前可以先忽略
查看lib目录下面,就有完备的动态库和静态库文件,除此之外,还必要有相应的头文件,这里没有安装,功能有所欠缺。
  1. ubuntu->bpf-sop:$ ll lib/ -h
  2. -rw-r--r-- 1 ubuntu ubuntu  65K Mar 28 23:30 libbpfjit.a
  3. -rw-r--r-- 1 ubuntu ubuntu  65K Mar 28 23:30 libbpfjit_pic.a
  4. lrwxrwxrwx 1 ubuntu ubuntu   18 Mar 28 23:30 libbpfjit.so -> libbpfjit.so.1.0.0
  5. lrwxrwxrwx 1 ubuntu ubuntu   18 Mar 28 23:30 libbpfjit.so.1 -> libbpfjit.so.1.0.0
  6. -rw-r--r-- 1 ubuntu ubuntu  55K Mar 28 23:30 libbpfjit.so.1.0.0
  7. -rw-r--r-- 1 ubuntu ubuntu 471K Mar 28 23:30 libsljit.a
  8. -rw-r--r-- 1 ubuntu ubuntu 474K Mar 28 23:30 libsljit_pic.a
  9. lrwxrwxrwx 1 ubuntu ubuntu   17 Mar 28 23:30 libsljit.so -> libsljit.so.1.0.0
  10. lrwxrwxrwx 1 ubuntu ubuntu   17 Mar 28 23:30 libsljit.so.1 -> libsljit.so.1.0.0
  11. -rw-r--r-- 1 ubuntu ubuntu 275K Mar 28 23:30 libsljit.so.1.0.0
复制代码
2.2 编写tcpdump-mini程序

第一个文件ether-input.c,用于初始化原始套接字,从网卡读取原始以太报文。
  1. // ether-input.c 初始化Raw套接字,并且收取原始报文。
  2. extern int32_t ether_sock_init(const char *if_name);
  3. extern int32_t ether_recv_packet(int32_t sock, char *buffer, int32_t len);
复制代码
第二个文件mini-tcpdump.c,实现抓包过滤处理逻辑,将tcpdump过滤参数转换为bpf和机器指令,打印符合条件的报文信息。
  1. // min-tcpdump.c 处理参数和过滤,打印目标报文信息
  2. int32_t deal_tcpdump_code(pcap_t **handle, struct bpf_program *fp, const char *filter_exp);
  3. void print_packet_info(const char *packet);
  4. int32_t capture_packets(int32_t sock, struct bpf_program *fp, bpfjit_func_t code);
  5. int32_t main(int32_t argc, char *argv[])
  6. {
  7.     int32_t            sock;
  8.     pcap_t            *handle;
  9.     struct bpf_program fp;
  10.     bpfjit_func_t      code;
  11.     if (argc != 3) {
  12.         fprintf(stderr, "Usage: %s <interface> <tcpdump code>\n", argv[0]);
  13.         return -1;
  14.     }
  15.     sock   = 0;
  16.     handle = NULL;
  17.     code   = NULL;
  18.     memset(&fp, 0, sizeof(struct bpf_program));
  19.     /* 也许用一下伪Lambda函数?至少可以避免全局变量使用 */
  20.     lambda (void, free_source, int32_t signo) {
  21.         /* 打印提示信息, 回收资源 */
  22.         if (signo != -1) {
  23.             printf("\nCtrl+C is pressed(sig %d), exit with 0.\n", signo);
  24.         }
  25.         if (fp.bf_insns) {
  26.             pcap_freecode(&fp);
  27.         }
  28.         if (handle) {
  29.             pcap_close(handle);
  30.         }
  31.         if (code) {
  32.             bpfjit_free_code(code);
  33.         }
  34.         if (sock) {
  35.             close(sock);
  36.         }
  37.         exit(0);
  38.     }
  39.     /* 注册ctrl+c信号处理函数 */
  40.     signal(SIGINT, free_source);
  41.     printf("Try to dump packet from interface(%s) with filter(%s)\n", argv[1], argv[2]);
  42.     sock = ether_sock_init(argv[1]);
  43.     if (sock < 0) {
  44.         fprintf(stderr, "Failed to init socket\n");
  45.         free_source(-1);
  46.         return -1;
  47.     }
  48.     /* 编译tcpdump参数为bpf指令码 */
  49.     if (deal_tcpdump_code(&handle, &fp, argv[2]) != 0) {
  50.         fprintf(stderr, "Failed to deal tcpdump code\n");
  51.         free_source(-1);
  52.         return -1;
  53.     }
  54.     /* 验证bpf指令码的正确性 */
  55.     if (bpf_validate(fp.bf_insns, fp.bf_len) == 0) {
  56.         fprintf(stderr, "Failed to validate bpf code\n");
  57.         free_source(-1);
  58.         return -1;
  59.     }
  60.     /* 编译bpf指令码为机器指令 */
  61.     code = bpfjit_generate_code(NULL, fp.bf_insns, fp.bf_len);
  62.     if (code == 0) {
  63.         fprintf(stderr, "Failed to compile bpf code\n");
  64.         free_source(-1);
  65.         return -1;
  66.     }
  67.     /* 循环抓包到结束 */
  68.     if (capture_packets(sock, &fp, code) != 0) {
  69.         fprintf(stderr, "Failed to capture packets\n");
  70.         free_source(-1);
  71.         return -1;
  72.     }
  73.     return 0;
  74. }
复制代码
在mini-tcpdump程序主函数里面,实行了如下操作流程:
这个函数的重要目标是从指定的网络接口抓取数据包,并根据指定的tcpdump代码进行过滤和处理。它使用了一些库函数和自界说函数来实现这些功能,并在程序实行过程中处理了一些错误情况,以确保程序的稳定性和正确性。
2.3 对比bpf-filter和bpfjit的开销

除了正常验证bpf过滤功能之外,这里还简单对比了一下函数解析BPF指令码和JIT即使编译实行的性能:
  1. /* 获取绝对时间差值 */
  2. static inline int64_t get_current_time(void)
  3. {
  4.     struct timespec ts;
  5.     clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  6.     return ts.tv_sec * 1000000000 + ts.tv_nsec;
  7. }
  8. int32_t capture_packets(int32_t sock, struct bpf_program *fp, bpfjit_func_t code)
  9. {
  10.     int32_t ret, temp;
  11.     int64_t start_time, end_time;
  12.     int64_t filtered_count, captured_count;
  13.     int64_t func_cost_time, jit_cost_time;
  14.     char    buffer[PACKET_SIZE];
  15.     func_cost_time = jit_cost_time = 0;
  16.     filtered_count = captured_count = 0;
  17.     printf("Start to capture packets...\n");
  18.     while (1) {
  19.         int32_t len = ether_recv_packet(sock, buffer, PACKET_SIZE);
  20.         if (len < 0) {
  21.             perror("Failed to receive packet");
  22.             break;
  23.         }
  24.         /* 执行BPF过滤器函数 */
  25.         start_time = get_current_time();
  26.         ret        = bpf_filter(fp->bf_insns, (const u_char *)buffer, len, len);
  27.         end_time   = get_current_time();
  28.         func_cost_time += end_time - start_time;
  29.         /* 执行BPF过滤即时编译指令 */
  30.         start_time = get_current_time();
  31.         temp       = jitcall(code, (const u_char *)buffer, len, len);
  32.         end_time   = get_current_time();
  33.         jit_cost_time += end_time - start_time;
  34.         if (temp != ret) {
  35.             fprintf(stderr,
  36.                 "Warning, Result of executing bpf jit code is not equal to filter func: %d -> "
  37.                 "%d.\n",
  38.                 ret, temp);
  39.             return -1;
  40.         }
  41.         if (ret == 0) {
  42.             filtered_count++;
  43.             continue;
  44.         }
  45.         captured_count++;
  46.         /* 打印抓到的报文信息 */
  47.         printf("[%ld]Packet captured ! Bypass %ld, Time avg cost: %ld ns(func) - %ld ns(jit).\n",
  48.             captured_count, filtered_count, func_cost_time / (captured_count + filtered_count),
  49.             jit_cost_time / (captured_count + filtered_count));
  50.         print_packet_info(buffer);
  51.     }
  52.     return -1;
  53. }
复制代码
这个函数用于捕获网络数据包并实行BPF过滤器函数和即时编译指令:
这个函数的重要目标是捕获数据包并实行BPF过滤器函数和即时编译指令,以实现网络数据包的过滤和处理功能。
2.4 实际效果演示

起首抓取一下icmp报文看看,如下:
  1. onceday->bpf-sop:# export LD_LIBRARY_PATH=./lib
  2. onceday->bpf-sop:# ./mini-tcpdump eth0 "icmp"
  3. Try to dump packet from interface(eth0) with filter(icmp)
  4. BPF bytecode length: 6
  5. BPF bytecode:
  6. 28, 00, 00, 0c
  7. 15, 00, 03, 800
  8. 30, 00, 00, 17
  9. 15, 00, 01, 01
  10. 06, 00, 00, 2000
  11. 06, 00, 00, 00
  12. (000) ldh      [12]
  13. (001) jeq      #0x800           jt 2    jf 5
  14. (002) ldb      [23]
  15. (003) jeq      #0x1             jt 4    jf 5
  16. (004) ret      #8192
  17. (005) ret      #0
  18. Start to capture packets...
  19. [1]Packet captured ! Bypass 20, Time avg cost: 932 ns(func) - 204 ns(jit).
  20.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  21.         IP: 169.254.128.17 -> 10.0.4.17, Proto: 1, Total Length: 28.
  22. [2]Packet captured ! Bypass 20, Time avg cost: 895 ns(func) - 197 ns(jit).
  23.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  24.         IP: 10.0.4.17 -> 169.254.128.17, Proto: 1, Total Length: 28.
  25. ^C
  26. Ctrl+C is pressed(sig 2), exit with 0.
复制代码
表达式复杂度可以再高一些(捕获目标IP为169.254.0.4的80端口TCP报文,或者ICMP报文):
  1. onceday->bpf-sop:# export LD_LIBRARY_PATH=./lib
  2. onceday->bpf-sop:# ./mini-tcpdump eth0 "(tcp port 80 and host 169.254.0.4) or icmp"
  3. Try to dump packet from interface(eth0) with filter((tcp port 80 and host 169.254.0.4) or icmp)
  4. BPF bytecode length: 50
  5. BPF bytecode:
  6. 28, 00, 00, 0c
  7. 15, 00, 06, 86dd
  8. 30, 00, 00, 14
  9. 15, 00, 04, 06
  10. 28, 00, 00, 36
  11. 15, 0e, 00, 50
  12. 28, 00, 00, 38
  13. 15, 0c, 00, 50
  14. 28, 00, 00, 0c
  15. 15, 00, 22, 800
  16. 30, 00, 00, 17
  17. 15, 00, 20, 06
  18. 28, 00, 00, 14
  19. 45, 1e, 00, 1fff
  20. b1, 00, 00, 0e
  21. 48, 00, 00, 0e
  22. 15, 03, 00, 50
  23. b1, 00, 00, 0e
  24. 48, 00, 00, 10
  25. 15, 00, 18, 50
  26. 28, 00, 00, 0c
  27. 15, 00, 02, 800
  28. 20, 00, 00, 1a
  29. 15, 18, 00, a9fe0004
  30. 28, 00, 00, 0c
  31. 15, 00, 02, 800
  32. 20, 00, 00, 1e
  33. 15, 14, 00, a9fe0004
  34. 28, 00, 00, 0c
  35. 15, 00, 02, 806
  36. 20, 00, 00, 1c
  37. 15, 10, 00, a9fe0004
  38. 28, 00, 00, 0c
  39. 15, 00, 02, 806
  40. 20, 00, 00, 26
  41. 15, 0c, 00, a9fe0004
  42. 28, 00, 00, 0c
  43. 15, 00, 02, 8035
  44. 20, 00, 00, 1c
  45. 15, 08, 00, a9fe0004
  46. 28, 00, 00, 0c
  47. 15, 00, 02, 8035
  48. 20, 00, 00, 26
  49. 15, 04, 00, a9fe0004
  50. 28, 00, 00, 0c
  51. 15, 00, 03, 800
  52. 30, 00, 00, 17
  53. 15, 00, 01, 01
  54. 06, 00, 00, 2000
  55. 06, 00, 00, 00
  56. (000) ldh      [12]
  57. (001) jeq      #0x86dd          jt 2    jf 8
  58. (002) ldb      [20]
  59. (003) jeq      #0x6             jt 4    jf 8
  60. (004) ldh      [54]
  61. (005) jeq      #0x50            jt 20   jf 6
  62. (006) ldh      [56]
  63. (007) jeq      #0x50            jt 20   jf 8
  64. (008) ldh      [12]
  65. (009) jeq      #0x800           jt 10   jf 44
  66. (010) ldb      [23]
  67. (011) jeq      #0x6             jt 12   jf 44
  68. (012) ldh      [20]
  69. (013) jset     #0x1fff          jt 44   jf 14
  70. (014) ldxb     4*([14]&0xf)
  71. (015) ldh      [x + 14]
  72. (016) jeq      #0x50            jt 20   jf 17
  73. (017) ldxb     4*([14]&0xf)
  74. (018) ldh      [x + 16]
  75. (019) jeq      #0x50            jt 20   jf 44
  76. (020) ldh      [12]
  77. (021) jeq      #0x800           jt 22   jf 24
  78. (022) ld       [26]
  79. (023) jeq      #0xa9fe0004      jt 48   jf 24
  80. (024) ldh      [12]
  81. (025) jeq      #0x800           jt 26   jf 28
  82. (026) ld       [30]
  83. (027) jeq      #0xa9fe0004      jt 48   jf 28
  84. (028) ldh      [12]
  85. (029) jeq      #0x806           jt 30   jf 32
  86. (030) ld       [28]
  87. (031) jeq      #0xa9fe0004      jt 48   jf 32
  88. (032) ldh      [12]
  89. (033) jeq      #0x806           jt 34   jf 36
  90. (034) ld       [38]
  91. (035) jeq      #0xa9fe0004      jt 48   jf 36
  92. (036) ldh      [12]
  93. (037) jeq      #0x8035          jt 38   jf 40
  94. (038) ld       [28]
  95. (039) jeq      #0xa9fe0004      jt 48   jf 40
  96. (040) ldh      [12]
  97. (041) jeq      #0x8035          jt 42   jf 44
  98. (042) ld       [38]
  99. (043) jeq      #0xa9fe0004      jt 48   jf 44
  100. (044) ldh      [12]
  101. (045) jeq      #0x800           jt 46   jf 49
  102. (046) ldb      [23]
  103. (047) jeq      #0x1             jt 48   jf 49
  104. (048) ret      #8192
  105. (049) ret      #0
  106. Start to capture packets...
  107. [1]Packet captured ! Bypass 7, Time avg cost: 368 ns(func) - 95 ns(jit).
  108.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  109.         IP: 169.254.128.6 -> 10.0.4.17, Proto: 1, Total Length: 28.
  110. [2]Packet captured ! Bypass 7, Time avg cost: 353 ns(func) - 94 ns(jit).
  111.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  112.         IP: 10.0.4.17 -> 169.254.128.6, Proto: 1, Total Length: 28.
  113. [3]Packet captured ! Bypass 27, Time avg cost: 1170 ns(func) - 284 ns(jit).
  114.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  115.         IP: 169.254.128.17 -> 10.0.4.17, Proto: 1, Total Length: 28.
  116. [4]Packet captured ! Bypass 27, Time avg cost: 1138 ns(func) - 277 ns(jit).
  117.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  118.         IP: 10.0.4.17 -> 169.254.128.17, Proto: 1, Total Length: 28.
  119. [5]Packet captured ! Bypass 55, Time avg cost: 1283 ns(func) - 302 ns(jit).
  120.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  121.         IP: 169.254.128.6 -> 10.0.4.17, Proto: 1, Total Length: 28.
  122. [6]Packet captured ! Bypass 55, Time avg cost: 1264 ns(func) - 298 ns(jit).
  123.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  124.         IP: 10.0.4.17 -> 169.254.128.6, Proto: 1, Total Length: 28.
  125. [7]Packet captured ! Bypass 68, Time avg cost: 1345 ns(func) - 324 ns(jit).
  126.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  127.         IP: 10.0.4.17/50252 -> 169.254.0.4/80, TCP(6), Total Length: 60.
  128. [8]Packet captured ! Bypass 69, Time avg cost: 1338 ns(func) - 322 ns(jit).
  129.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  130.         IP: 169.254.0.4/80 -> 10.0.4.17/50252, TCP(6), Total Length: 52.
  131. [9]Packet captured ! Bypass 69, Time avg cost: 1325 ns(func) - 319 ns(jit).
  132.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  133.         IP: 10.0.4.17/50252 -> 169.254.0.4/80, TCP(6), Total Length: 40.
  134. [10]Packet captured ! Bypass 69, Time avg cost: 1312 ns(func) - 315 ns(jit).
  135.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  136.         IP: 10.0.4.17/50252 -> 169.254.0.4/80, TCP(6), Total Length: 221.
  137. [11]Packet captured ! Bypass 70, Time avg cost: 1302 ns(func) - 312 ns(jit).
  138.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  139.         IP: 169.254.0.4/80 -> 10.0.4.17/50252, TCP(6), Total Length: 40.
  140. [12]Packet captured ! Bypass 70, Time avg cost: 1290 ns(func) - 310 ns(jit).
  141.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  142.         IP: 10.0.4.17/50252 -> 169.254.0.4/80, TCP(6), Total Length: 835.
  143. [13]Packet captured ! Bypass 71, Time avg cost: 1278 ns(func) - 308 ns(jit).
  144.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  145.         IP: 169.254.0.4/80 -> 10.0.4.17/50252, TCP(6), Total Length: 40.
  146. [14]Packet captured ! Bypass 72, Time avg cost: 1272 ns(func) - 306 ns(jit).
  147.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  148.         IP: 169.254.0.4/80 -> 10.0.4.17/50252, TCP(6), Total Length: 256.
  149. [15]Packet captured ! Bypass 72, Time avg cost: 1261 ns(func) - 304 ns(jit).
  150.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  151.         IP: 10.0.4.17/50252 -> 169.254.0.4/80, TCP(6), Total Length: 40.
  152. [16]Packet captured ! Bypass 72, Time avg cost: 1249 ns(func) - 301 ns(jit).
  153.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  154.         IP: 169.254.0.4/80 -> 10.0.4.17/50252, TCP(6), Total Length: 40.
  155. [17]Packet captured ! Bypass 73, Time avg cost: 1227 ns(func) - 296 ns(jit).
  156.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  157.         IP: 10.0.4.17/50252 -> 169.254.0.4/80, TCP(6), Total Length: 40.
  158. [18]Packet captured ! Bypass 74, Time avg cost: 1220 ns(func) - 295 ns(jit).
  159.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  160.         IP: 169.254.0.4/80 -> 10.0.4.17/50252, TCP(6), Total Length: 40.
  161. [19]Packet captured ! Bypass 84, Time avg cost: 1248 ns(func) - 302 ns(jit).
  162.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  163.         IP: 169.254.128.17 -> 10.0.4.17, Proto: 1, Total Length: 28.
  164. [20]Packet captured ! Bypass 84, Time avg cost: 1238 ns(func) - 299 ns(jit).
  165.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  166.         IP: 10.0.4.17 -> 169.254.128.17, Proto: 1, Total Length: 28.
  167. ^C
  168. Ctrl+C is pressed(sig 2), exit with 0.
复制代码
可以更进一步抓包,以下tcpdump表达式来捕获所有含有SYN标识的TCP报文:
  1. onceday->bpf-sop:# export LD_LIBRARY_PATH=./lib
  2. onceday->bpf-sop:# ./mini-tcpdump eth0 "tcp[tcpflags] & (tcp-syn) != 0"
  3. Try to dump packet from interface(eth0) with filter(tcp[tcpflags] & (tcp-syn) != 0)
  4. BPF bytecode length: 38
  5. BPF bytecode:
  6. 28, 00, 00, 0c
  7. 15, 00, 23, 800
  8. 28, 00, 00, 0c
  9. 15, 00, 06, 86dd
  10. 30, 00, 00, 14
  11. 15, 08, 00, 06
  12. 30, 00, 00, 14
  13. 15, 00, 02, 2c
  14. 30, 00, 00, 36
  15. 15, 04, 00, 06
  16. 28, 00, 00, 0c
  17. 15, 00, 19, 800
  18. 30, 00, 00, 17
  19. 15, 00, 17, 06
  20. 28, 00, 00, 14
  21. 45, 15, 00, 1fff
  22. 00, 00, 00, 0d
  23. 02, 00, 00, 00
  24. b1, 00, 00, 0e
  25. 60, 00, 00, 00
  26. 0c, 00, 00, 00
  27. 07, 00, 00, 00
  28. 50, 00, 00, 0e
  29. 02, 00, 00, 01
  30. 00, 00, 00, 02
  31. 02, 00, 00, 02
  32. 61, 00, 00, 02
  33. 60, 00, 00, 01
  34. 5c, 00, 00, 00
  35. 02, 00, 00, 02
  36. 00, 00, 00, 00
  37. 02, 00, 00, 03
  38. 61, 00, 00, 03
  39. 60, 00, 00, 02
  40. 1c, 00, 00, 00
  41. 15, 01, 00, 00
  42. 06, 00, 00, 2000
  43. 06, 00, 00, 00
  44. (000) ldh      [12]
  45. (001) jeq      #0x800           jt 2    jf 37
  46. (002) ldh      [12]
  47. (003) jeq      #0x86dd          jt 4    jf 10
  48. (004) ldb      [20]
  49. (005) jeq      #0x6             jt 14   jf 6
  50. (006) ldb      [20]
  51. (007) jeq      #0x2c            jt 8    jf 10
  52. (008) ldb      [54]
  53. (009) jeq      #0x6             jt 14   jf 10
  54. (010) ldh      [12]
  55. (011) jeq      #0x800           jt 12   jf 37
  56. (012) ldb      [23]
  57. (013) jeq      #0x6             jt 14   jf 37
  58. (014) ldh      [20]
  59. (015) jset     #0x1fff          jt 37   jf 16
  60. (016) ld       #0xd
  61. (017) st       M[0]
  62. (018) ldxb     4*([14]&0xf)
  63. (019) ld       M[0]
  64. (020) add      x
  65. (021) tax      
  66. (022) ldb      [x + 14]
  67. (023) st       M[1]
  68. (024) ld       #0x2
  69. (025) st       M[2]
  70. (026) ldx      M[2]
  71. (027) ld       M[1]
  72. (028) and      x
  73. (029) st       M[2]
  74. (030) ld       #0x0
  75. (031) st       M[3]
  76. (032) ldx      M[3]
  77. (033) ld       M[2]
  78. (034) sub      x
  79. (035) jeq      #0x0             jt 37   jf 36
  80. (036) ret      #8192
  81. (037) ret      #0
  82. Start to capture packets...
  83. [1]Packet captured ! Bypass 33, Time avg cost: 990 ns(func) - 208 ns(jit).
  84.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  85.         IP: 10.0.4.17/33886 -> 13.107.5.93/443, TCP(6), Total Length: 60.
  86. [2]Packet captured ! Bypass 34, Time avg cost: 1009 ns(func) - 211 ns(jit).
  87.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  88.         IP: 10.0.4.17/41920 -> 169.254.0.4/80, TCP(6), Total Length: 60.
  89. [3]Packet captured ! Bypass 35, Time avg cost: 1035 ns(func) - 216 ns(jit).
  90.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  91.         IP: 169.254.0.4/80 -> 10.0.4.17/41920, TCP(6), Total Length: 52.
  92. [4]Packet captured ! Bypass 41, Time avg cost: 1183 ns(func) - 214 ns(jit).
  93.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  94.         IP: 13.107.5.93/443 -> 10.0.4.17/33886, TCP(6), Total Length: 52.
  95. [5]Packet captured ! Bypass 85, Time avg cost: 1273 ns(func) - 236 ns(jit).
  96.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  97.         IP: 10.0.4.17/41926 -> 169.254.0.4/80, TCP(6), Total Length: 60.
  98. [6]Packet captured ! Bypass 86, Time avg cost: 1274 ns(func) - 236 ns(jit).
  99.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  100.         IP: 169.254.0.4/80 -> 10.0.4.17/41926, TCP(6), Total Length: 52.
  101. [7]Packet captured ! Bypass 311, Time avg cost: 610 ns(func) - 134 ns(jit).
  102.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  103.         IP: 10.0.4.17/33902 -> 13.107.5.93/443, TCP(6), Total Length: 60.
  104. [8]Packet captured ! Bypass 312, Time avg cost: 612 ns(func) - 134 ns(jit).
  105.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  106.         IP: 13.107.5.93/443 -> 10.0.4.17/33902, TCP(6), Total Length: 52.
  107. [9]Packet captured ! Bypass 343, Time avg cost: 673 ns(func) - 145 ns(jit).
  108.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  109.         IP: 10.0.4.17/33906 -> 13.107.5.93/443, TCP(6), Total Length: 60.
  110. [10]Packet captured ! Bypass 344, Time avg cost: 677 ns(func) - 145 ns(jit).
  111.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  112.         IP: 13.107.5.93/443 -> 10.0.4.17/33906, TCP(6), Total Length: 52.
  113. [11]Packet captured ! Bypass 400, Time avg cost: 775 ns(func) - 162 ns(jit).
  114.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  115.         IP: 10.0.4.17/41930 -> 169.254.0.4/80, TCP(6), Total Length: 60.
  116. [12]Packet captured ! Bypass 401, Time avg cost: 779 ns(func) - 163 ns(jit).
  117.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  118.         IP: 169.254.0.4/80 -> 10.0.4.17/41930, TCP(6), Total Length: 52.
  119. [13]Packet captured ! Bypass 422, Time avg cost: 807 ns(func) - 168 ns(jit).
  120.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  121.         IP: 10.0.4.17/42400 -> 169.254.0.4/80, TCP(6), Total Length: 60.
  122. [14]Packet captured ! Bypass 423, Time avg cost: 808 ns(func) - 168 ns(jit).
  123.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  124.         IP: 169.254.0.4/80 -> 10.0.4.17/42400, TCP(6), Total Length: 52.
  125. [15]Packet captured ! Bypass 447, Time avg cost: 835 ns(func) - 173 ns(jit).
  126.         Ethernet: 52:54:00:85:f0:22 -> fe:ee:8f:bf:86:99, Type : 0x0800
  127.         IP: 10.0.4.17/42402 -> 169.254.0.4/80, TCP(6), Total Length: 60.
  128. [16]Packet captured ! Bypass 448, Time avg cost: 837 ns(func) - 173 ns(jit).
  129.         Ethernet: fe:ee:8f:bf:86:99 -> 52:54:00:85:f0:22, Type : 0x0800
  130.         IP: 169.254.0.4/80 -> 10.0.4.17/42402, TCP(6), Total Length: 52.
  131. ^C
  132. Ctrl+C is pressed(sig 2), exit with 0.
复制代码
2.5 tcpdump-mini源码文件

2.5.1 ether-input.c文件。

  1. #define _GNU_SOURCE
  2. #include <stdio.h>
  3. #include <stdint.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <errno.h>
  7. #include <unistd.h>
  8. #include <sys/socket.h>
  9. #include <sys/ioctl.h>
  10. #include <net/if.h>
  11. #include <netinet/in.h>
  12. #include <linux/if_packet.h>
  13. #include <linux/if_ether.h>
  14. #define BUFFER_SIZE 65536
  15. extern int32_t ether_sock_init(const char *if_name);
  16. extern int32_t ether_recv_packet(int32_t sock, char *buffer, int32_t len);
  17. /**
  18. * @description: 初始化原始套接字
  19. * @param {char} *if_name 接口名称
  20. * @return {sock} 返回套接字ID
  21. */
  22. int32_t ether_sock_init(const char *if_name)
  23. {
  24.     int32_t            sock;
  25.     struct ifreq       ifr;
  26.     struct sockaddr_ll sll;
  27.     /* 创建原始套接字, 抓取所有二层协议的报文, 不限于以太网协议 */
  28.     sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  29.     if (sock < 0) {
  30.         perror("Failed to create socket");
  31.         exit(1);
  32.     }
  33.     /* 获取网络接口的索引 */
  34.     memset(&ifr, 0, sizeof(ifr));
  35.     strncpy(ifr.ifr_name, if_name, IFNAMSIZ - 1);
  36.     if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
  37.         perror("Failed to get interface index by ioctl");
  38.         close(sock);
  39.         exit(1);
  40.     }
  41.     /* 绑定到指定的网络接口 */
  42.     memset(&sll, 0, sizeof(sll));
  43.     sll.sll_family   = AF_PACKET;
  44.     sll.sll_ifindex  = ifr.ifr_ifindex;
  45.     sll.sll_protocol = htons(ETH_P_ALL);
  46.     if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
  47.         perror("Failed to bind to interface");
  48.         close(sock);
  49.         exit(1);
  50.     }
  51.     return sock;
  52. }
  53. /**
  54. * @description: 收取报文
  55. * @param {int32_t} sock
  56. * @param {char} *buffer
  57. * @param {int32_t} len
  58. * @return {*}
  59. */
  60. int32_t ether_recv_packet(int32_t sock, char *buffer, int32_t len)
  61. {
  62.     int32_t ret;
  63.     ret = recvfrom(sock, buffer, len, 0, NULL, NULL);
  64.     if (ret < 0) {
  65.         perror("Failed to receive packet");
  66.         return -1;
  67.     }
  68.     return ret;
  69. }
复制代码
2.5.2 mini-tcpdump.c文件

  1. #define _GNU_SOURCE
  2. #include <pcap.h>
  3. #include <stdio.h>
  4. #include <stdint.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <signal.h>
  8. #include <time.h>
  9. #include <netpacket/packet.h>
  10. #include <net/ethernet.h>
  11. #include <netinet/ip.h>
  12. #include <netinet/tcp.h>
  13. #include <netinet/udp.h>
  14. #include "bpfjit.h"
  15. #include "bpf-compat.h"
  16. /* clang-format off */
  17. #ifndef __COMPILING
  18. /* 让ide不会报错, 可能无法识别嵌套函数语法 */
  19. #define lambda(ret, name, arg)   ret (*name)(arg); name = NULL ; for (arg; 0;)
  20. #else
  21. #define lambda(ret, name, ...)   ret name(__VA_ARGS__)
  22. #endif
  23. /* clang-format on */
  24. /* 执行即时编译的汇编指令 */
  25. #define jitcall(func, _pkt, _wirelen, _buflen) \
  26.     (func(NULL, &((bpf_args_t){.pkt = _pkt, .wirelen = _wirelen, .buflen = _buflen})))
  27. extern int32_t ether_sock_init(const char *if_name);
  28. extern int32_t ether_recv_packet(int32_t sock, char *buffer, int32_t len);
  29. #define PACKET_SIZE 65536
  30. /**
  31. * @description: 根据Tcpdump过滤表达式生成BPF字节码
  32. * @param {pcap_t} **handler 句柄
  33. * @param {bpf_program *} fp BPF程序
  34. * @param {char} *filter_exp 过滤表达式
  35. * @return {*}
  36. */
  37. int32_t deal_tcpdump_code(pcap_t **handle, struct bpf_program *fp, const char *filter_exp)
  38. {
  39.     int         i;
  40.     bpf_u_int32 net;
  41.     pcap_t     *temp_handle;
  42.     char errbuf[PCAP_ERRBUF_SIZE];
  43.     /* The IP of our sniffing device */
  44.     net = 0;
  45.     /* 使用pcap_open_dead()创建一个用于编译过滤器的空PCAP句柄 */
  46.     temp_handle = pcap_open_dead(DLT_EN10MB, BUFSIZ);
  47.     if (temp_handle == NULL) {
  48.         fprintf(stderr, "Couldn't create dead pcap session: %s\n", errbuf);
  49.         return -1;
  50.     }
  51.     /* 编译BPF过滤器,但不应用到任何捕获会话 */
  52.     if (pcap_compile(temp_handle, fp, filter_exp, 0, net) == -1) {
  53.         fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(temp_handle));
  54.         pcap_close(temp_handle);
  55.         return -1;
  56.     }
  57.     printf("BPF bytecode length: %d\n", fp->bf_len);
  58.     printf("BPF bytecode: \n");
  59.     for (i = 0; i < fp->bf_len; i++) {
  60.         printf("%02x, %02x, %02x, %02x\n", fp->bf_insns[i].code, fp->bf_insns[i].jt,
  61.             fp->bf_insns[i].jf, fp->bf_insns[i].k);
  62.     }
  63.     /* 打印bpf字节码 */
  64.     bpf_dump(fp, 1);
  65.     *handle = temp_handle;
  66.     return 0;
  67. }
  68. /* 获取绝对时间差值 */
  69. static inline int64_t get_current_time(void)
  70. {
  71.     struct timespec ts;
  72.     clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  73.     return ts.tv_sec * 1000000000 + ts.tv_nsec;
  74. }
  75. /**
  76. * @description: 打印报文信息
  77. * @param {u_char} *packet
  78. * @return {*}
  79. */
  80. void print_packet_info(const char *packet)
  81. {
  82.     struct ether_header *eth_header;
  83.     struct ip           *ip_header;
  84.     char                 src_ip[INET_ADDRSTRLEN], dst_ip[INET_ADDRSTRLEN];
  85.     struct tcphdr       *tcp_header;
  86.     struct udphdr       *udp_header;
  87.     /*  以太网头部 */
  88.     eth_header = (struct ether_header *)packet;
  89.     printf(
  90.         "\tEthernet: %02x:%02x:%02x:%02x:%02x:%02x -> %02x:%02x:%02x:%02x:%02x:%02x, Type : 0x%04x "
  91.         "\n",
  92.         eth_header->ether_shost[0], eth_header->ether_shost[1], eth_header->ether_shost[2],
  93.         eth_header->ether_shost[3], eth_header->ether_shost[4], eth_header->ether_shost[5],
  94.         eth_header->ether_dhost[0], eth_header->ether_dhost[1], eth_header->ether_dhost[2],
  95.         eth_header->ether_dhost[3], eth_header->ether_dhost[4], eth_header->ether_dhost[5],
  96.         ntohs(eth_header->ether_type));
  97.     /* 非IP协议直接Pass */
  98.     if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) {
  99.         printf("Not an IP packet\n");
  100.         return;
  101.     }
  102.     ip_header = (struct ip *)(packet + sizeof(struct ether_header));
  103.     inet_ntop(AF_INET, &ip_header->ip_src, src_ip, INET_ADDRSTRLEN);
  104.     inet_ntop(AF_INET, &ip_header->ip_dst, dst_ip, INET_ADDRSTRLEN);
  105.     /* 分IP类型打印信息 */
  106.     switch (ip_header->ip_p) {
  107.     case IPPROTO_TCP:
  108.         tcp_header = (struct tcphdr *)(ip_header + 1);
  109.         printf("\tIP: %s/%d -> %s/%d, TCP(%d), Total Length: %d.\n", src_ip,
  110.             ntohs(tcp_header->th_sport), dst_ip, ntohs(tcp_header->th_dport), ip_header->ip_p,
  111.             ntohs(ip_header->ip_len));
  112.         break;
  113.     case IPPROTO_UDP:
  114.         udp_header = (struct udphdr *)(ip_header + 1);
  115.         printf("\tIP: %s/%d -> %s/%d, UDP(%d), Total Length: %d.\n", src_ip,
  116.             ntohs(udp_header->uh_sport), dst_ip, ntohs(udp_header->uh_dport), ip_header->ip_p,
  117.             ntohs(ip_header->ip_len));
  118.         break;
  119.     default:
  120.         printf("\tIP: %s -> %s, Proto: %d, Total Length: %d.\n", src_ip, dst_ip, ip_header->ip_p,
  121.             ntohs(ip_header->ip_len));
  122.         break;
  123.     }
  124.     return;
  125. }
  126. /**
  127. * @description: 抓包函数
  128. * @param {int32_t} sock 套接字
  129. * @param {struct bpf_program} *fp BPF程序
  130. * @return {*}
  131. */
  132. int32_t capture_packets(int32_t sock, struct bpf_program *fp, bpfjit_func_t code)
  133. {
  134.     int32_t ret, temp;
  135.     int64_t start_time, end_time;
  136.     int64_t filtered_count, captured_count;
  137.     int64_t func_cost_time, jit_cost_time;
  138.     char    buffer[PACKET_SIZE];
  139.     func_cost_time = jit_cost_time = 0;
  140.     filtered_count = captured_count = 0;
  141.     printf("Start to capture packets...\n");
  142.     while (1) {
  143.         int32_t len = ether_recv_packet(sock, buffer, PACKET_SIZE);
  144.         if (len < 0) {
  145.             perror("Failed to receive packet");
  146.             break;
  147.         }
  148.         /* 执行BPF过滤器函数 */
  149.         start_time = get_current_time();
  150.         ret        = bpf_filter(fp->bf_insns, (const u_char *)buffer, len, len);
  151.         end_time   = get_current_time();
  152.         func_cost_time += end_time - start_time;
  153.         /* 执行BPF过滤即时编译指令 */
  154.         start_time = get_current_time();
  155.         temp       = jitcall(code, (const u_char *)buffer, len, len);
  156.         end_time   = get_current_time();
  157.         jit_cost_time += end_time - start_time;
  158.         if (temp != ret) {
  159.             fprintf(stderr,
  160.                 "Warning, Result of executing bpf jit code is not equal to filter func: %d -> "
  161.                 "%d.\n",
  162.                 ret, temp);
  163.             return -1;
  164.         }
  165.         if (ret == 0) {
  166.             filtered_count++;
  167.             continue;
  168.         }
  169.         captured_count++;
  170.         /* 打印抓到的报文信息 */
  171.         printf("[%ld]Packet captured ! Bypass %ld, Time avg cost: %ld ns(func) - %ld ns(jit).\n",
  172.             captured_count, filtered_count, func_cost_time / (captured_count + filtered_count),
  173.             jit_cost_time / (captured_count + filtered_count));
  174.         print_packet_info(buffer);
  175.     }
  176.     return -1;
  177. }
  178. int32_t main(int32_t argc, char *argv[])
  179. {
  180.     int32_t            sock;
  181.     pcap_t            *handle;
  182.     struct bpf_program fp;
  183.     bpfjit_func_t      code;
  184.     if (argc != 3) {
  185.         fprintf(stderr, "Usage: %s <interface> <tcpdump code>\n", argv[0]);
  186.         return -1;
  187.     }
  188.     sock   = 0;
  189.     handle = NULL;
  190.     code   = NULL;
  191.     memset(&fp, 0, sizeof(struct bpf_program));
  192.     /* 也许用一下伪Lambda函数?至少可以避免全局变量使用 */
  193.     lambda (void, free_source, int32_t signo) {
  194.         /* 打印提示信息, 回收资源 */
  195.         if (signo != -1) {
  196.             printf("\nCtrl+C is pressed(sig %d), exit with 0.\n", signo);
  197.         }
  198.         if (fp.bf_insns) {
  199.             pcap_freecode(&fp);
  200.         }
  201.         if (handle) {
  202.             pcap_close(handle);
  203.         }
  204.         if (code) {
  205.             bpfjit_free_code(code);
  206.         }
  207.         if (sock) {
  208.             close(sock);
  209.         }
  210.         exit(0);
  211.     }
  212.     /* 注册ctrl+c信号处理函数 */
  213.     signal(SIGINT, free_source);
  214.     printf("Try to dump packet from interface(%s) with filter(%s)\n", argv[1], argv[2]);
  215.     sock = ether_sock_init(argv[1]);
  216.     if (sock < 0) {
  217.         fprintf(stderr, "Failed to init socket\n");
  218.         free_source(-1);
  219.         return -1;
  220.     }
  221.     /* 编译tcpdump参数为bpf指令码 */
  222.     if (deal_tcpdump_code(&handle, &fp, argv[2]) != 0) {
  223.         fprintf(stderr, "Failed to deal tcpdump code\n");
  224.         free_source(-1);
  225.         return -1;
  226.     }
  227.     /* 验证bpf指令码的正确性 */
  228.     if (bpf_validate(fp.bf_insns, fp.bf_len) == 0) {
  229.         fprintf(stderr, "Failed to validate bpf code\n");
  230.         free_source(-1);
  231.         return -1;
  232.     }
  233.     /* 编译bpf指令码为机器指令 */
  234.     code = bpfjit_generate_code(NULL, fp.bf_insns, fp.bf_len);
  235.     if (code == 0) {
  236.         fprintf(stderr, "Failed to compile bpf code\n");
  237.         free_source(-1);
  238.         return -1;
  239.     }
  240.     /* 循环抓包到结束 */
  241.     if (capture_packets(sock, &fp, code) != 0) {
  242.         fprintf(stderr, "Failed to capture packets\n");
  243.         free_source(-1);
  244.         return -1;
  245.     }
  246.     return 0;
  247. }
复制代码
2.5.3 Makefile文件

  1. CC=gcc
  2. TARGET=mini-tcpdump
  3. .PHONY: all clean build
  4. all: clean build
  5. clean:
  6.         rm -f $(TARGET)
  7. build: $(TARGET)
  8. SOURCE=mini-tcpdump.c ether-input.c
  9. CFLAGS=-Wall -Werror -O0 -g -D__COMPILING=1
  10. INCLUDE=-I./bpfjit/src -I./bpfjit/sljit/sljit_src
  11. LDFLAGS= -L./lib -lsljit -lbpfjit -lpcap
  12. mini-tcpdump: $(SOURCE)
  13.         $(CC) -o $@ $^ $(CFLAGS) $(INCLUDE) $(LDFLAGS)
复制代码
3. 总结(mini-tcpdump演示GIF)

根据MVP最小可用产物(tcpdump-mini)的验证结果,使用bpfjit的过滤效率还是挺不错的,复杂表达式下,开销都小于1us。如下:
场景bpf filterbpf jitICMP过滤900ns200nsICMP和TCP-80过滤1200ns300nsTCP SYN过滤800ns160ns 实际测试过程中,抓包越多,实行效率会更高,所以这里的耗时数据可作为一个参考值,但不能直接用于基准性能测试
从数据中,明显可以看出,bpfjit效率比bpf-filter要高,耗时只有bpf-filter的20~30%左右








   Once Day

  

    也信美人终作土,不堪幽梦太匆匆......
    如果这篇文章为您带来了帮助或开导,不妨点个赞




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4