eBPF 和 Go 入门 |Go主题月

eBPF 和 Go 入门 |Go主题月eBPF拥有一个蓬勃发展的生态系统,在eBPF自己 及其各种应用(包括 XDP)上都有大量的学习资源。但在选择与eBPF交互和协调的库和工具时,它开始变得令人困惑。在这里,你必须在基于Python的 BCC框架、基于C语言的libbpf以及Dropbox, Cilium, Aq…

Golang 外文翻译任务原文:Getting Started with eBPF and Go 作者:Michael Kashin

eBPF拥有一个蓬勃发展的生态系统,在eBPF自己 及其各种应用(包括 XDP)上都有大量的学习资源。但在选择与eBPF交互和协调的库和工具时,它开始变得令人困惑。在这里,你必须在基于Python的 BCC框架、基于C语言的libbpf以及Dropbox, Cilium, AquaCalico.等一系列基于Go的库中进行选择。eBPF代码的 “生产化”是另一个经常被忽视的重要领域,即从手动测试应用走向Cilium等生产级应用。在这篇文章中,我将记录我在这个领域的一些发现,特别是在用Go编写的用户空间控制器编写网络(XDP)应用的背景下。

选择 eBPF 库

在大多数情况下,eBPF库是为了帮助你实现两件事。

  • 将eBPF程序和映射(maps)加载到内核中,并执行重定位,通过文件描述符将一个eBPF程序和正确的映射(maps)关联起来。
  • 与eBPF映射(maps)进行交互,允许对存储在这些映射(maps)中的键/值对进行所有标准的CRUD操作。

一些库还可以帮助您将您的eBPF程序附加到一个特定的钩子( hook)上,尽管对于网络用例来说,这可以通过任何现有的netlink API库轻松完成。

当谈到eBPF库的选择时,我不是唯一一个困惑的人(见[1],[2])。事实上,每个库都有自己独特的范围和限制:

  • Calico 实现了一个围绕 bpftool 和 iproute2 的 CLI 命令的 Go 封装器。
  • Aqua 实现了一个围绕 libbpf C 库的 Go 封装器。
  • Dropbox 支持的程序不多,但有一个非常简洁方便的用户API。
  • IO Visor的 gobpf 是BCC框架的go绑定集合,它更侧重于跟踪和剖析。
  • Cilium 和Cloudflare正在维护一个纯Go库(以下简称 libbpf-go),它将所有的eBPF系统调用抽象在一个原生Go接口后面。

对于我的特定网络用例的选择,尽管我真的很喜欢(简单的)Dropbox的那个本来也可以使用的的用例,但我最终选择使用了libbpf-go,因为它被Cilium和Cloudflare使用,并且有一个活跃的社区,。

为了熟悉开发流程,我决定实现一个XDP交叉连接应用,它在网络拓扑仿真方面有一个非常小众但重要的用例。目标是有一个应用程序,它可以观察一个配置文件,并确保本地接口根据该文件的YAML规范进行互连。下面是xdp-xconnect工作原理的高级概述。

img

下面的章节将逐步描述应用程序的构建和交付过程,更多关注集成,而不是实际代码。 xdp-xconnect完整代码可以在Github上找到。

步骤1 -编写eBPF代码

通常这步将是任何一篇 “eBPF入门 “文章的主要部分,然而这次不是。我不认为我可以帮助别人学习如何编写eBPF,但是我可以介绍一些非常好的能做到这些的资源。

  • ebpf.io和Cilium的eBPF和XDP参考指南上有很多详细的通用eBPF理论。
  • 对于eBPF和XDP的一些实际操作。最好的是*xdp-tutorial*。这是一个惊人的资源,即使你最终不做任务,也绝对值得阅读。
  • Cilium源码和它的分析在 [1] [2].

我的eBPF程序非常简单,它包括一个对eBPFhelper函数的单次调用,该函数根据输入接口的索引将所有数据包从一个接口重定向到另一个接口:

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("xdp")
int  xdp_xconnect(struct xdp_md *ctx)
{
    return bpf_redirect_map(&xconnect_map, ctx->ingress_ifindex, 0);
}

为了编译上面的程序,我们需要为所有包含的头文件提供搜索路径,最简单的方法是将所有的文件复制到linux/tools/lib/bpf/下,但是,这样会包含很多不必要的文件。所以另一个替代方法是创建一个依赖列表:

$ clang -MD -MF xconnect.d -target bpf -I ~/linux/tools/lib/bpf -c xconnect.c

现在我们可以只对xconnect.d中指定的少量文件进行本地复制,并使用下面的命令来编译本地CPU架构的eBPF代码:

$ clang -target bpf -Wall -O2 -emit-llvm -g -Iinclude -c xconnect.c -o - | \
llc -march=bpf -mcpu=probe -filetype=obj -o xconnect.o

生成的ELF文件正是我们下一步需要提供给Go库的文件。

步骤2 -编写Go代码

编译后的eBPF程序和map可以通过libbpf-go加载,只需几条指令。通过添加一个带有eBPF标签的结构,我们可以自动完成重定位过程,这样我们的程序就知道在哪里可以找到它的map。

spec, err := ebpf.LoadCollectionSpec("ebpf/xconnect.o")
if err != nil {
  panic(err)
}

var objs struct {
	XCProg  *ebpf.Program `ebpf:"xdp_xconnect"`
	XCMap   *ebpf.Map     `ebpf:"xconnect_map"`
}
if err := spec.LoadAndAssign(&objs, nil); err != nil {
	panic(err)
}
defer objs.XCProg.Close()
defer objs.XCMap.Close()

ebpf.Map类型具有一组对加载的map的内容执行标准CRUD操作的方法:

err = objs.XCMap.Put(uint32(0), uint32(10))

var v0 uint32
err = objs.XCMap.Lookup(uint32(0), &v0)

err = objs.XCMap.Delete(uint32(0))

libbpf-go唯一没有涵盖的步骤是将程序附加到网络钩子(network hooks)上。然而,任何现有的netlink库,例如vishvananda/netlink,都可以通过将网络链接与加载程序的文件描述符关联起来而轻松实现。

link, err := netlink.LinkByName("eth0")
err = netlink.LinkSetXdpFdWithFlags(*link, c.objs.XCProg.FD(), 2)

注意,我使用SKB_MODE XDP标志来绕过退出veth驱动的注意事项。虽然原生的XDP模式比任何其他eBPF钩子都要快得多,但由于包头必须由网络堆栈预先解析,所以SKB_MODE可能没有那么快(见视频)。

步骤3 -代码分发

此时,如果不是因为一个问题——eBPF代码的可移植性,那么一切都应该已经准备好,可以打包并发布我们的应用程序了。从历史上看,这个过程涉及到将eBPF源代码复制到目标平台,拉入所需的内核头文件,并针对特定的内核版本进行编译。这个问题在跟踪/监控/分析用例中尤为突出,因为这些用例可能需要访问几乎所有的内核数据结构,所以唯一的解决方案就是引入另一层间接性(参见CO-RE)。

另一方面,网络用例依赖于相对较小且稳定的内核类型子集,所以它们不会像它们的跟踪和剖析用例那样遭受同样的问题。根据我目前所看到的情况,最常见的两种代码打包方法是。

  • 将eBPF代码与所需的内核头文件一起发送,假设它们与底层内核相匹配(参见Cilium)。
  • 发送eBPF代码,并在目标平台上拉入内核头文件。

在这两种情况下,eBPF代码仍然要在该目标平台上编译,这是在用户空间应用启动前需要执行的额外步骤。然而,还有一个替代方案,那就是预编译eBPF代码,只运载ELF文件。这正是bpf2go可以做到的,它可以将编译后的代码嵌入到一个Go包中。它依靠go generate生成一个新的文件,其中包含编译后的eBPF和libpf-go骨架代码,唯一的要求就是//go:generator指令。不过一旦生成,我们的eBPF程序只需要几行就可以加载(注意没有任何参数)。

specs, err := newXdpSpecs()
objs, err := specs.Load(nil)

这种方法最明显的好处是,我们不再需要在目标机器上编译,可以在一个包或Go二进制中同时发布eBPF和用户空间Go代码。这很好,因为它允许我们不仅将我们的应用程序作为二进制文件使用,而且还可以将其导入到任何第三方的Go应用程序中(见使用示例)。

相关阅读及有趣的参考资料

Generic Theory: github.com/xdp-project… docs.cilium.io/en/stable/b… qmonnet.github.io/whirl-offlo…

BCC and libbpf: facebookmicrosites.github.io/bpf/blog/20… nakryiko.com/posts/libbp… pingcap.com/blog/why-we… facebookmicrosites.github.io/bpf/blog/

eBPF/XDP performance: www.netronome.com/blog/bpf-eb…

Linus Kernel Coding Style: www.kernel.org/doc/html/v5…

libbpf-go example programs: github.com/takehaya/go… github.com/hrntknr/nfN… github.com/takehaya/Vi… github.com/tcfw/vpc github.com/florianl/tc… github.com/cloudflare/… github.com/b3a-dev/ebp…

bpf2go: github.com/lmb/ship-bp… pkg.go.dev/github.com/…

XDP example programs: github.com/cpmarvin/ln… gitlab.com/mwiget/crpd…

原文:Getting Started with eBPF and Go 作者:Michael Kashin

今天的文章eBPF 和 Go 入门 |Go主题月分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14794.html

(0)
编程小号编程小号

相关推荐

发表回复

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