源起

最近看到国内两篇文章[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/

BPF漫谈的更多相关文章

  1. 【道德经】漫谈实体、对象、DTO及AutoMapper的使用

    写在前面 实体和值对象 实体和对象 故常无欲以观其妙,常有欲以观其徼 初始实体和演化实体 代码中的DTO AutoMapper实体转换 后记 实体(Entity).对象(Object).DTO(Dat ...

  2. CSS实现水平|垂直居中漫谈

    利用CSS进行元素的水平居中,比较简单,手到擒来:行级元素设置其父元素的text-align center,块级元素设置其本身的left 和 right margins为auto即可.而撸起垂直居中, ...

  3. 【转】漫谈iOS程序的证书和签名机制

    转自:漫谈iOS程序的证书和签名机制 接触iOS开发半年,曾经也被这个主题坑的摸不着头脑,也在淘宝上买过企业证书签名这些服务,有大神都做了一个全自动的发布打包(不过此大神现在不卖企业证书了),甚是羡慕 ...

  4. UP board 漫谈(1)——从Atom到UP Board

    title: UP board 漫谈(1)--从Atom到UP Board date: 2016-12-26 12:33:03 tags: UP board categories: 开发板 perma ...

  5. 漫谈C++11 Thread库之原子操作

    我在之前一篇博文<漫谈C++11 Thread库之使写多线程程序>中,着重介绍了<thread>头文件中的std::thread类以及其上的一些基本操作,至此我们动手写多线程程 ...

  6. 漫谈可视化Prefuse(六)---改动源码定制边粗细

    可视化一路走来,体会很多:博客一路写来,收获颇丰:代码一路码来,思路越来越清晰.终究还是明白了一句古话:纸上得来终觉浅,绝知此事要躬行. 跌跌撞撞整合了个可视化小tool,零零碎碎结交了众多的志同道合 ...

  7. 漫谈可视化Prefuse(五)---一款属于我自己的可视化工具

    伴随着前期的基础积累,翻过API,读过一些Demo,总觉得自己已经摸透了Prefuse,小打小闹似乎已经无法满足内心膨胀的自己.还记得儿时看的<武状元苏乞儿>中降龙十八掌最后一张居然是空白 ...

  8. 漫谈可视化Prefuse(四)---被玩坏的Prefuse API

    这个双12,别人都在抢红包.逛淘宝.上京东,我选择再续我的“漫谈可视化”系列(好了,不装了,其实是郎中羞涩...) 上篇<漫谈可视化Prefuse(三)---Prefuse API数据结构阅读有 ...

  9. 漫谈可视化Prefuse(三)---Prefuse API数据结构阅读有感

    前篇回顾:上篇<漫谈可视化Prefuse(二)---一分钟学会Prefuse>主要通过一个Prefuse的具体实例了解了构建一个Prefuse application的具体步骤.一个Pre ...

随机推荐

  1. 批处理之 for/f 详解

    含有/F的for格式:FOR /F ["options"] %%i IN (file) DO command FOR /F ["options"] %%i IN ...

  2. Mybatis Dynamic Query 2.0.2

    项目地址:https://github.com/wz2cool/mybatis-dynamic-query 文档地址:https://wz2cool.gitbooks.io/mybatis-dynam ...

  3. Nim or not Nim? hdu3032 SG值打表找规律

    Nim or not Nim? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)T ...

  4. bzoj3624(铺黑白路)(并查集维护)

    题意网上自己随便找,绝对是找的到的. 题解:(白边表示鹅卵石路,黑边表示水泥路)这道题的解法,先考虑将黑边所有都先连起来,组成一个又一个的联通块,然后用白边去连, 如果可以联通的话,就用白边去代替黑边 ...

  5. 关于Android路由的实现

    先说一下背景,目前有需求从外部包括其他应用和WEB跳转到我们自己的APP,就这么个简单的需求-- 要实现这种外部跳转的功能,我们可以理解为打算跳转的一方有多少方式通知到APP进行相对的响应行为.所以, ...

  6. php中常用的字符串长度函数strlen()与mb_strlen()实例解释

    int strlen ( string $string )  int strlen ( string $string )  获取给定字符串的[字节]长度 成功则返回字符串$string的长度,如果$s ...

  7. [C语言]贪吃蛇_结构数组实现

    一.设计思路 蛇身本质上就是个结构数组,数组里存储了坐标x.y的值,再通过一个循环把它打印出来,蛇的移动则是不断地刷新重新打印.所以撞墙.咬到自己只是数组x.y值的简单比较. 二.用上的知识点 结构数 ...

  8. python之路第二篇(基础篇)

    入门知识: 一.关于作用域: 对于变量的作用域,执行声明并在内存中存在,该变量就可以在下面的代码中使用. if 10 == 10: name = 'allen' print name 以下结论对吗? ...

  9. Windows下Apache添加SSL模块

    参考资料:http://www.yuansir-web.com/2011/05/12/hello-world/测试环境:windows2003 32位 + Apache2.4 + PHP5.4 一.准 ...

  10. C#下的两种加密方式MD5和DEC

    md5加密 /// <summary>    /// MD5加密    /// </summary>    /// <param name="toCryStri ...