首页 > 编程知识 正文

xdp ebpf(ebpf原理)

时间:2023-05-04 06:42:12 阅读:100390 作者:3215

编辑:https://jvns.ca/blog/2017/06/28/notes-on-bpf-埃布夫/作者:怀德姆埃文斯

翻译:qhwdw

今天,在我最喜欢的meetup网站上有一篇我很喜欢的文章!sukra Sharma[1](@ tuxeology[2]在twitter/github上)写了一篇关于传统BPF和Linux中新加入的eBPF的优秀讨论文章,这让我想写一个eBPF程序!

本文是—— BSD包过滤:一种新的用户级包捕获架构[3]

我想根据讨论写一些笔记,因为我觉得很棒!

首先,这里有一张幻灯片[4]和一个pdf[5]。这个pdf非常好。结尾有一些链接。你可以直接在PDF中点击这个链接。

什么是 BPF?

在BPF出现之前,如果你想做包过滤,你必须先把所有的包复制到用户空间,然后才能过滤它们(使用“点击”)。

这样做有两个问题:

如果在用户空间进行过滤,意味着会将所有的包复制到用户空间,复制数据的成本非常昂贵。

使用的过滤算法效率非常低。

问题#1的解决方案似乎显而易见,那就是将过滤逻辑移到内核。(具体实现细节虽然不太清楚,稍后再讨论)

但是为什么过滤算法效率低呢?

如果运行tcpdump host foo,它实际上会运行一个相当复杂的查询。用下面的树来描述它:

评估树有点复杂。因此,可以用一种更简单的方式来表示这棵树,如下所示:

然后,如果设置ether.type=IP和ip.src=foo,就必须明白匹配的包是host foo,其他的就不用查了。因此,这种数据结构(它们被称为“控制流图”或“CFG”)是表示您真正想要执行匹配检查的程序的最佳方式,而不是使用以前的树。

为什么 BPF 要工作在内核中

这里的关键点是包只是一个字节数组。BPF程序在这些字节的数组上运行。他们不被允许有循环,但是他们可以有聪明的方法知道IP头(IPv6和IPv4长度不同)并根据它们的长度找到TCP端口:

x=ip_header_length端口=*(packet_start x port_offset)

他们看起来不一样,但基本上是一样的。这篇论文/幻灯片中有关于虚拟机的非常详细的描述,所以我不打算解释。

rxddp运行tcpdump主机foo后发生了什么?据我理解,应该是以下过程。

将主机foo转换为高效的DAG规则。

那个DAG规则被转换成BPF虚拟机的BPF程序(BPF字节码)。

将BPF字节码发送到Linux内核,由内核验证。

这个编译BPF字节码的程序是本地代码。例如,这是ARM上的JIT代码[6]和x86的机器代码[7]

当包进入时,Linux运行本机代码来决定是否过滤包。对于每个需要处理的包,通常只需要运行100-200条CPU指令就可以完成,速度非常快!

现状:eBPF

毕竟BPF已经存在很久了!现在,我们可以有一个更令人兴奋的东西,那就是eBPF。我以前听说过eBPF,但我认为最好像这样把这些片段放在一起(我在4月的netdev上写了这篇XDP eBPF的文章[8]回复)

关于eBPF的一些事实是:

EBPF程序有自己的字节码语言,它们被编译成该字节码语言的内核本机代码,就像BPF程序一样。

EBPF在内核中运行

EBPF程序不能随意访问内核内存。相反,我们使用内核提供的函数来获取所需内容的一些严格限制的子集。

他们可以通过BPF映射与用户空间中的程序进行通信。

这是Linux 3.18的bpf系统调用。

kprobes 和 eBPF

您可以在Linux内核中选择一个函数(任意函数),然后运行您编写的程序,以便在每次调用该函数时运行。这看起来是不是很神奇?

例如,这里有一个名为diskssnoop[9]的BPF程序,它的函数是r

xddp开始/完成写入一个块到磁盘时,触发它执行跟踪。下图是它的代码片断:

BPF_HASH(start, struct request *);

void trace_start(struct pt_regs *ctx, struct request *req) {

// stash start timestamp by request ptr

u64 ts = bpf_ktime_get_ns();

start.update(&req, &ts);

}

...

b.attach_kprobe(event="blk_start_request", fn_name="trace_start")

b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_start")

本质上它声明一个 BPF 哈希(它的作用是当请求开始/完成时,这个程序去触发跟踪),一个名为 trace_start 的函数将被编译进 BPF 字节码,然后附加 trace_start 到内核函数 blk_start_request 上。

这里使用的是 bcc 框架,它可以让你写 Python 式的程序去生成 BPF 代码。你可以在 https://github.com/iovisor/bcc 找到它(那里有非常多的示例程序)。

uprobes 和 eBPF

因为我知道可以附加 eBPF 程序到内核函数上,但是,我不知道能否将 eBPF 程序附加到用户空间函数上!那会有更多令人激动的事情。这是 在 Python 中使用一个 eBPF 程序去计数 malloc 调用的示例[10]。

附加 eBPF 程序时应该考虑的事情

带 XDP 的网卡(我之前写过关于这方面的文章)

tc egress/ingress (在网络栈上)

kprobes(任意内核函数)

uprobes(很明显,任意用户空间函数??像带调试符号的任意 C 程序)

probes 是为 dtrace 构建的名为 “USDT probes” 的探针(像 这些 mysql 探针[11])。这是一个 使用 dtrace 探针的示例程序[12]

JVM[13]

跟踪点

seccomp / landlock 安全相关的事情

等等

这个讨论超级棒

在幻灯片里有很多非常好的链接,并且在 iovisor 仓库里有个 LINKS.md[14]。虽然现在已经很晚了,但是我马上要去写我的第一个 eBPF 程序了!


via: https://jvns.ca/blog/2017/06/28/notes-on-bpf---ebpf/

作者:wydbm Evans [15] 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

点击“了解更多”可访问文内链接

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。