zoukankan      html  css  js  c++  java
  • 【译】eBPF 和 Go 经验初探

    原文地址:https://networkop.co.uk/post/2021-03-ebpf-intro/
    首发地址: 【译】eBPF 和 Go 经验初探
    本站相关文档:使用 Go 语言管理和分发 ebpf 程序

    1. 前言

    eBPF 的生态欣欣向荣,无论是 eBPF 本身及其各种应用(包括 XDP) 方面都有大量的学习资源。但当涉及到选择库和工具来与 eBPF 进行交互时,会让人有所困惑。在选择时,你必须在基于 Python 的 BCC 框架、基于 C 的 libbpf 和一系列基于 Go 的 DropboxCiliumAquaCalico 等库中选择。另一个经常被忽视的重要领域是 eBPF 代码的 "生产化",即从手动编写的样例到生产级应用(例如 Cilium)。在本篇文章中,我将记录相关的经验,特别是在网络(XDP)应用程序场景中,使用 Go 编写的用户空间控制程序。

    2. 选择 eBPF 库

    在大多数情况下,eBPF 库主要协助实现两个功能:

    • 将 eBPF 程序和 Map 载入内核并执行重定位,通过其文件描述符将 eBPF 程序与正确的 Map 进行关联。

    • 与 eBPF Map 交互,允许对存储在 Map 中的键/值对进行标准的 CRUD 操作。

    部分库也可以帮助你将 eBPF 程序附加到一个特定的钩子,尽管对于网络场景下,这可能很容易采用现有的 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 接口后面。

    基于我的网络特定用例,我最终选择了 libbpf-go,因为其被 Cilium 和 Cloudflare 使用,并且有一个活跃的社区,尽管我也非常喜欢简单易用的 Dropbox 库,并且也可以使用它。

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

    img

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

    3. 步骤1 - 编写 eBPF 代码

    通常情况下,这将是任何 "eBPF 入门" 文章的主要部分,然而这一次它并不是重点。我并不认为自己可以帮助别人学习如何编写eBPF,然而,我可以参考一些非常好的资源。

    • 通用的 eBPF 理论在网站 ebpf.io 和 Cilium 的 eBPF 和 XDP 参考指南中有大量的细节。
    • 对 eBPF 和 XDP 进行实践的最好地方是 xdp-tutorial。这是一个了不起的资源,即使你最终选择不完成作业,也绝对值得阅读。
    • Cilium 的源代码和其在 [1][2] 的分析。

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

    #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
    

    The resulting ELF file is what we’d need to provide to our Go library in the next step.

    编译生成的 ELF 文件就是我们在下一步需要提供给 Go 库的程序。

    4. 步骤 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 包含的步骤是将程序附加到网络钩子上。然而,这可以通过任何现有的 netlink 库轻松实现,例如vishvananda/netlink,通过将网络连接与加载程序的文件描述符联系起来:

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

    请注意,我使用 SKB_MODE XDP 标志来绕过退出的 veth 驱动程序 caveat 。尽管本地 XDP 模式比任何其他 eBPF 钩子快得多,但 SKB_MODE 可能没有那么快,因为数据包头必须由网络栈预先解析(见视频)。

    5. 步骤 3 - 代码分发

    在这一点上,如果不是因为一个问题 -- eBPF 代码可移植性,一切都应该已经准备好打包和发布应用。历史上,这个过程涉及将 eBPF 源代码复制到目标平台,拉取所需的内核头文件,并为特定的内核版本进行编译。这个问题对于追踪/监控/跟踪的用例尤其明显,因为这些用例可能需要访问几乎所有的内核数据结构,所以唯一的解决办法是引入中介层(见 CO-RE)。

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

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

    在这两种情况下,eBPF 代码仍然需要在目标平台上编译,这是一个额外的步骤,需要在用户空间应用程序启动之前进行。然而,还有一个选择,那就是预先将 eBPF 代码编译成 ELF 格式文件,最终只分发 ELF 文件。这正是 bpf2go 可以做到的,它可以将编译后的代码嵌入到 Go 包中。其依靠 go generate 注解指令产生一个新的文件,其中包含编译好的 eBPF 和 libbpf-go 脚手架代码,唯一的要求是 //go:generate 指令。一旦生成,我们的 eBPF 程序只需几行就可以被加载(注意没有任何参数)。

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

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

    6. 阅读和有趣的参考资料

    通用理论:
    https://github.com/xdp-project/xdp-tutorial
    https://docs.cilium.io/en/stable/bpf/
    https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/

    BCC 和 libbpf:
    https://facebookmicrosites.github.io/bpf/blog/2020/02/20/bcc-to-libbpf-howto-guide.html
    https://nakryiko.com/posts/libbpf-bootstrap/
    https://pingcap.com/blog/why-we-switched-from-bcc-to-libbpf-for-linux-bpf-performance-analysis
    https://facebookmicrosites.github.io/bpf/blog/

    eBPF/XDP 性能:
    https://www.netronome.com/blog/bpf-ebpf-xdp-and-bpfilter-what-are-these-things-and-what-do-they-mean-enterprise/

    Linus Kernel 代码风格:
    https://www.kernel.org/doc/html/v5.9/process/coding-style.html

    libbpf-go 样例程序:
    https://github.com/takehaya/goxdp-template
    https://github.com/hrntknr/nfNat
    https://github.com/takehaya/Vinbero
    https://github.com/tcfw/vpc
    https://github.com/florianl/tc-skeleton
    https://github.com/cloudflare/rakelimit
    https://github.com/b3a-dev/ebpf-geoip-demo

    bpf2go
    https://github.com/lmb/ship-bpf-with-go
    https://pkg.go.dev/github.com/cilium/ebpf/cmd/bpf2go

    XDP 样例程序:
    https://github.com/cpmarvin/lnetd-ctl
    https://gitlab.com/mwiget/crpd-l2tpv3-xdp

  • 相关阅读:
    团队作业2
    团队作业2
    软件工程-团队作业1
    软件工程第一次作业
    自我介绍
    软件工程第一次作业
    Python3中采用PyInstaller打包工程项目
    mol2分子库文件拆分成单个mol2文件
    化学信息包安装
    单词读音音频源网址
  • 原文地址:https://www.cnblogs.com/davad/p/yiebpf-he-go-jing-yan-chu-tan.html
Copyright © 2011-2022 走看看