zoukankan      html  css  js  c++  java
  • BPF漫谈

    源起

    最近看到国内两篇文章[1][2]先后翻译了就职于Netflix的性能分析大牛Brendan Gregg于2017年7月31日写的《Golang bcc/BPF Function Tracing》[3],这迅速引起了我的兴趣,2016年时我曾在做MQTT服务器端开发时便意识到软件调试及动态追踪技术的重要性,其间研究春哥(章亦春 agentzh)的《动态追踪技术漫谈》[4]时,文中提及“最近几年 Linux 的主线开发者们,把原来用于防火墙的 netfilter 里所使用的动态编译器,即 BPF,扩展了一下,得到了一个所谓的 eBPF,可以作为某种更加通用的内核虚拟机。”,当时并不能理解这其中的意义所在,便没有深入了解,直到最近看到这两篇文章后,我重新进行了相关的研究,并意识到这项技术所将影响到的领域。

    BPF 初窥

    既然春哥提到Linux,那就先从《Linux Socket Filtering aka Berkeley Packet Filter (BPF)》[5]开始,文中提及:

    在Linux中BPF比在BSD中更加简单,人们不需要关心设备,你只需要创建你的filter代码,然后通过SO_ATTACH_FILTER的socket选项将其发送给内核,如果你的代码通过内核的检查,那么你就可以立即在那个socket上开始过滤数据。你也可以通过SO_LOCK_FILTER来锁住你attach到socket上的filter。

    BPF最大的用户可能是libpcap,执行一个高级别的过滤器命令行像tcpdump -i em1 port 22会通过libpcap内部的编译器会生成BPF代码通过SO_ATTACH_FILTER发往内核。

    tcpdump可以以不同的形式来显示生成的BPF代码,下面我将其man page列出来:

    -d Dump the compiled packet-matching code in a human readable form to standard output and stop.

    -d 选项会输出人类可读的包匹配代码(即汇编形式的BPF代码,下文中我将详述)。

    -dd Dump packet-matching code as a C program fragment.

    -dd 选项输出可用于C程序的包匹配代码。

    -ddd Dump packet-matching code as decimal numbers (preceded with a count).

    -ddd 选项输出十进制的包匹配代码(最前面会输出代码的行数)

    上面关于tcpdump的内容可能你还一时无法理解,可以暂时跳过。

    尽管我们这里仅仅讲述了用于socket的BPF,但BPF已经用于Linux的很多方面,包括用于netfilter的xt_bpf(用于iptables),用于内核qdisc层的cls_bpf(用于tc,可参考tc-bpf [6]),SECCOMP-BPF (SECure COMPuting [7][8]),和其他许多地方,诸如team driver, PTP code等。

    之后文中指出原始的BPF论文,即 Steven McCanne 和 Van Jacobson 于1993年写的《The BSD packet filter: a new
    architecture for user-level packet capture》[9]。

    下面我将讲述原始论文中的重点部分:

    文中提及最早的Unix filter evaluator是基于栈来设计的,而BPF则使用了基于寄存器的filter evaluator,并且使用了一种straightforward的buffering策略,这使得其在同样的硬件上总体性能高于Sun的NIT的100倍。

    论文中,呈现了BPF的设计,概述了其如何与系统的其余部分进行交互,描绘了过滤机制的新方法,最后呈现了BPF、NIT、CSPF的性能度量,这显示出BPF性能快于其他方式的原因。

    论文的前半部分主要讲述了新老包过滤器设计上的差异,以及BPF过滤器因为这些设计所带来的性能上的巨大的提升。后文开始讲述BPF过滤器伪机的设计。这是本文的重点内容。我将结合上文中的Linux文档进行详细讲解。

    BPF 伪机及其汇编指令

    BPF伪机包括一个32位的累加器A,一个32位的索引寄存器X,一个16 x 32位的内存和一个隐含的程序计数器。

      Element          Description
    
      A                32 bit wide accumulator
      X                32 bit wide X register
      M[]              16 x 32 bit wide misc registers aka "scratch memory store", addressable from 0 to 15
    

    在这些元素上的操作可以被分为下面的类别:

    • LOAD 指令集拷贝一个值到A或X。
    • STORE 指令集拷贝A或X的值到内存。
    • ALU 指令集用X或常数作为操作数在累加器上执行算数或逻辑运算。
    • BRANCH 指令集根据常量或X与A的比较测试来改变控制流程。
    • RETURN 指令集终止过滤器并表明报文的哪一部分保留下来,如果返回0,报文全部被丢弃。
    • MISCELLANEOUS 指令集包含其他所有指令,当前是寄存器转移指令集。

    指令集为固定长度,格式如下:

    |    opcode:16    |  jt:8  |  jf:8  |
    |                k:32               |
    

    其中的每一部分解释如下:

    • opcode:操作码,16位,指明了具体的指令及其寻址模式。
    • jt:"jump if true",8位,用于条件跳转指令,指明测试成功时从下一条指令到跳转目标的偏移值。
    • jf:"jump if false",8位,用于条件跳转指令,指明测试失败时从下一条指令到跳转目标的偏移值。
    • k:32位,K的含义依据不同的操作码而不同。

    下表展示了定义于<linux/filter.h>的操作码及其寻址方式:

      Instruction      Addressing mode      Description
    
      ld               1, 2, 3, 4, 10       Load word into A
      ldi              4                    Load word into A
      ldh              1, 2                 Load half-word into A
      ldb              1, 2                 Load byte into A
      ldx              3, 4, 5, 10          Load word into X
      ldxi             4                    Load word into X
      ldxb             5                    Load byte into X
    
      st               3                    Store A into M[]
      stx              3                    Store X into M[]
    
      jmp              6                    Jump to label
      ja               6                    Jump to label
      jeq              7, 8                 Jump on A == k
      jneq             8                    Jump on A != k
      jne              8                    Jump on A != k
      jlt              8                    Jump on A <  k
      jle              8                    Jump on A <= k
      jgt              7, 8                 Jump on A >  k
      jge              7, 8                 Jump on A >= k
      jset             7, 8                 Jump on A &  k
    
      add              0, 4                 A + <x>
      sub              0, 4                 A - <x>
      mul              0, 4                 A * <x>
      div              0, 4                 A / <x>
      mod              0, 4                 A % <x>
      neg                                   !A
      and              0, 4                 A & <x>
      or               0, 4                 A | <x>
      xor              0, 4                 A ^ <x>
      lsh              0, 4                 A << <x>
      rsh              0, 4                 A >> <x>
    
      tax                                   Copy A into X
      txa                                   Copy X into A
    
      ret              4, 9                 Return
    

    下表展示了上表第二列的寻址方式的具体细节:

      Addressing mode  Syntax               Description
    
       0               x/%x                 Register X
       1               [k]                  BHW at byte offset k in the packet
       2               [x + k]              BHW at the offset X + k in the packet
       3               M[k]                 Word at offset k in M[]
       4               #k                   Literal value stored in k
       5               4*([k]&0xf)          Lower nibble * 4 at byte offset k in the packet
       6               L                    Jump label L
       7               #k,Lt,Lf             Jump to Lt if true, otherwise jump to Lf
       8               #k,Lt                Jump to Lt if predicate is true
       9               a/%a                 Accumulator A
      10               extension            BPF extension
    

    Linux内核有一些和load指令集一起使用的BPF扩展,它们通过“溢出”k的值为一个负的偏移值加一个特定的扩展偏移值来使用,这些BPF扩展的结果被保存到A中。可能的BPF扩展展示在下表:

      Extension                             Description
    
      len                                   skb->len
      proto                                 skb->protocol
      type                                  skb->pkt_type
      poff                                  Payload start offset
      ifidx                                 skb->dev->ifindex
      nla                                   Netlink attribute of type X with offset A
      nlan                                  Nested Netlink attribute of type X with offset A
      mark                                  skb->mark
      queue                                 skb->queue_mapping
      hatype                                skb->dev->type
      rxhash                                skb->hash
      cpu                                   raw_smp_processor_id()
      vlan_tci                              skb_vlan_tag_get(skb)
      vlan_avail                            skb_vlan_tag_present(skb)
      vlan_tpid                             skb->vlan_proto
      rand                                  prandom_u32()
    

    这些扩展可以以'#'为前缀。

    下面是Linux文档中给出的BPF汇编代码的例子:

    
    ** ARP packets:
    
      ldh [12]
      jne #0x806, drop
      ret #-1
      drop: ret #0
    
    ** IPv4 TCP packets:
    
      ldh [12]
      jne #0x800, drop
      ldb [23]
      jneq #6, drop
      ret #-1
      drop: ret #0
    
    ** (Accelerated) VLAN w/ id 10:
    
      ld vlan_tci
      jneq #10, drop
      ret #-1
      drop: ret #0
    
    ** icmp random packet sampling, 1 in 4
      ldh [12]
      jne #0x800, drop
      ldb [23]
      jneq #1, drop
      # get a random uint32 number
      ld rand
      mod #4
      jneq #1, drop
      ret #-1
      drop: ret #0
    
    ** SECCOMP filter example:
    
      ld [4]                  /* offsetof(struct seccomp_data, arch) */
      jne #0xc000003e, bad    /* AUDIT_ARCH_X86_64 */
      ld [0]                  /* offsetof(struct seccomp_data, nr) */
      jeq #15, good           /* __NR_rt_sigreturn */
      jeq #231, good          /* __NR_exit_group */
      jeq #60, good           /* __NR_exit */
      jeq #0, good            /* __NR_read */
      jeq #1, good            /* __NR_write */
      jeq #5, good            /* __NR_fstat */
      jeq #9, good            /* __NR_mmap */
      jeq #14, good           /* __NR_rt_sigprocmask */
      jeq #13, good           /* __NR_rt_sigaction */
      jeq #35, good           /* __NR_nanosleep */
      bad: ret #0             /* SECCOMP_RET_KILL_THREAD */
      good: ret #0x7fff0000   /* SECCOMP_RET_ALLOW */
    

    上面的BPF汇编代码可以被保存到一个文件中,然后通过bpfc[10]来生成netsniff-ng[11]、cls_bpf和tcpdump格式的代码。

    参考

    [1] http://colobu.com/2017/09/22/golang-bcc-bpf-function-tracing/?from=timeline
    [2] http://www.jianshu.com/p/f1781fc452f6
    [3] http://www.brendangregg.com/blog/2017-01-31/golang-bcc-bpf-function-tracing.html
    [4] https://openresty.org/posts/dynamic-tracing/
    [5] https://www.kernel.org/doc/Documentation/networking/filter.txt
    [6] http://man7.org/linux/man-pages/man8/tc-bpf.8.html
    [7] https://www.kernel.org/doc/Documentation/userspace-api/seccomp_filter.rst
    [8] http://man7.org/linux/man-pages/man2/seccomp.2.html
    [9] http://www.tcpdump.org/papers/bpf-usenix93.pdf
    [10] http://man7.org/linux/man-pages/man8/bpfc.8.html
    [11] http://netsniff-ng.org/

    作者:Hevienz
    出处:http://www.cnblogs.com/hymenz/
    知识共享许可协议
    本博客原创作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
  • 相关阅读:
    Win RT Webview获取cookie
    c#代码片段新建(sinppet)
    wp8.1启动协议
    移动开源框架
    Web开发工具箱
    比较2个字符串相似度
    js的继承
    mvc4开篇之BundleConfig(1)
    职业规划历程
    Redis Cluster管理
  • 原文地址:https://www.cnblogs.com/hymenz/p/7798543.html
Copyright © 2011-2022 走看看