BPF 漫谈

源起

前不久收看国内两篇作品[1][2]程序翻译了下车于Netflix的习性分析大拿Brendan
格雷戈g于20一柒年4月十二日写的《Golang bcc/BPF Function
Tracing》[3],那飞跃引起了本人的兴味,201陆年时作者曾在做MQTT服务器端开荒时便开掘到软件调节和测试及动态跟踪手艺的重中之重,其间研讨春哥(章亦春
agentzh)的《动态追踪能力漫谈》[4]时,文中聊到“这些年来 Linux
的主线开采者们,把本来用于防火墙的 netfilter 里所选取的动态编译器,即
BPF,扩大了须臾间,获得了2个所谓的
eBPF,能够作为某种更加通用的基业虚拟机。”,当时并不能够明了这其间的含义所在,便未有深切领会,直到日前看看那两篇作品后,我重新开始展览了有关的研究,并开采到那项才具所将震慑到的领域。

源起

近期看来国内两篇文章[1][2]次第翻译了新任于Netflix的属性分析大咖Brendan
格雷戈g于20一七年四月二三日写的《Golang bcc/BPF Function
Tracing》[3],这赶快引起了自身的兴味,201陆年时自笔者曾在做MQTT服务器端开拓时便开掘到软件调节和测试及动态追踪本领的最首要,其间研商春哥(章亦春
agentzh)的《动态追踪本领漫谈》[4]时,文中聊起“近几年来 Linux
的主线开辟者们,把本来用于防火墙的 netfilter 里所使用的动态编写翻译器,即
BPF,扩大了须臾间,获得了1个所谓的
eBPF,能够作为某种尤其通用的基业虚拟机。”,当时并不能够知道那当中的意思所在,便未有浓密理解,直到方今来看那两篇小说后,小编再一次进行了相关的钻研,并发现到这项技巧所将震慑到的园地。

BPF 初窥

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

在Linux中BPF比在BSD中国和越南社会主义共和国发简明,人们无需关爱设备,你只供给创设你的filter代码,然后经过SO_ATTACH_FILTEMurano的socket选项将其发送给内核,纵然你的代码通过基础的反省,那么您就足以马上在充裕socket上起来过滤数据。你也得以通过SO_LOCK_FILTE昂Cora来锁住你attach到socket上的filter。

BPF最大的用户恐怕是libpcap,实行2个尖端其他过滤器命令行像tcpdump -i em1 port 22会经过libpcap内部的编写翻译器会生成BPF代码通过SO_ATTACH_FILTE途睿欧发往内核。

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 选项输出10进制的包相称代码(最前头会输出代码的行数)

地点关于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于19玖三年写的《The BSD packet filter: a new
architecture for user-level packet capture》[9]。

下边小编将讲述原始故事集中的着重部分:

文中谈到最早的Unix filter
evaluator是依照栈来设计的,而BPF则采取了依据寄存器的filter
evaluator,并且动用了壹种straightforward的buffering计谋,那使得其在相同的硬件上完整品质高于Sun的NIT的拾0倍。

舆论中,展现了BPF的设计,概述了其何等与系统的别的部分实行交互,描绘了过滤机制的新章程,最终表现了BPF、NIT、CSPF的性情衡量,那显得出BPF质量快于其余艺术的因由。

舆论的前半片段至关心器重要描述了新老包过滤器设计上的差距,以及BPF过滤器因为这么些规划所带来的习性上的远大的进级。后文开首讲述BPF过滤器伪机的规划。那是本文的要害内容。笔者将结合上文中的Linux文档举行详细批注。

BPF 初窥

既然春哥提到Linux,那就先从《Linux Socket Filtering aka 贝克雷 Packet
Filter (BPF)》[5]发端,文中谈起:

在Linux中BPF比在BSD中尤为简明,人们无需关心设备,你只供给成立你的filter代码,然后经过SO_ATTACH_FILTELX570的socket选项将其发送给内核,借使您的代码通过基础的反省,那么你就可以立时在尤其socket上初阶过滤数据。你也得以通过SO_LOCK_FILTE猎豹CS六来锁住你attach到socket上的filter。

BPF最大的用户大概是libpcap,实行3个高级其余过滤器命令行像tcpdump -i em1 port 22会经过libpcap内部的编写翻译器会生成BPF代码通过SO_ATTACH_FILTELacrosse发往内核。

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 选项输出10进制的包相称代码(最前面会输出代码的行数)

上边境海关于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于19玖三年写的《The BSD packet filter: a new
architecture for user-level packet capture》[9]。

上边作者将讲述原始故事集中的入眼部分:

文中谈到最早的Unix filter
evaluator是基于栈来设计的,而BPF则使用了依据寄存器的filter
evaluator,并且选择了1种straightforward的buffering攻略,那使得其在一如既往的硬件上海市总体品质高于Sun的NIT的100倍。

随想中,呈现了BPF的筹划,概述了其怎么着与系统的其他部分举行互相,描绘了过滤机制的新章程,最后表现了BPF、NIT、CSPF的属性衡量,那突显出BPF质量快于其余格局的原故。

散文的前半片段入眼描述了新老包过滤器设计上的差别,以及BPF过滤器因为这一个陈设所推动的习性上的皇皇的升级换代。后文早先讲述BPF过滤器伪机的安排。那是本文的基本点内容。笔者将整合上文中的Linux文档举办详尽批注。

BPF 伪机及其汇编指令

BPF伪机包蕴一个三11个人的累加器A,三个三十一个人的目录寄存器X,2个1陆 x
33人的内部存款和储蓄器和3个带有的先后计数器。

  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的可比测试来退换调整流程。
  • RETU锐界N
    指令集终止过滤器并标明报文的哪1部分保留下来,假设重临0,报文全体被放弃。
  • MISCELLANEOUS 指令集带有其余兼具指令,当前是寄存器转移指令集。

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

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

里头的每1有的解释如下:

  • opcode:操作码,13个人,指明了实际的下令及其寻址形式。
  • jt:”jump if
    true”,八个人,用于标准跳转指令,指明测试成功时从下一条指令到跳转目的的偏移值。
  • jf:”jump if
    false”,5位,用于标准跳转指令,指明测试失利时从下一条指令到跳转指标的偏移值。
  • k:3一人,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汇编代码能够被保留到1个文本中,然后通过bpfc[10]来生成netsniff-ng[11]、cls_bpf和tcpdump格式的代码。

BPF 伪机及其汇编指令

BPF伪机包蕴二个三10位的累加器A,壹个叁十二个人的目录寄存器X,贰个16 x
叁15个人的内部存储器和3个涵盖的主次计数器。

  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 指令集拷贝2个值到A或X。
  • STORE 指令集拷贝A或X的值到内部存款和储蓄器。
  • ALU 指令集用X或常数作为操作数在累加器上实践算数或逻辑运算。
  • BRANCH 指令集依据常量或X与A的相比测试来改造调节流程。
  • RETU讴歌ZDXN
    指令集终止过滤器并标明报文的哪部分保存下去,假若重回0,报文全体被甩掉。
  • MISCELLANEOUS 指令集带有别的具备指令,当前是寄存器转移指令集。

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

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

内部的每一有些解释如下:

  • opcode:操作码,16个人,指明了切实可行的指令及其寻址形式。
  • jt:”jump if
    true”,七个人,用于标准跳转指令,指明测试成功时从下一条指令到跳转目的的偏移值。
  • jf:”jump if
    false”,7个人,用于标准跳转指令,指明测试退步时从下一条指令到跳转目的的偏移值。
  • k:三拾3位,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的值为2个负的偏移值加一个特定的庞大偏移值来采纳,那些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汇编代码能够被保存到1个文件中,然后通过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/

 

参考

[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/

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图