eBPF

698 阅读12分钟

几个基础背景知识

内核空间和用户空间

要想充分理解eBPF的设计思想,对于Unix和Linux的内核空间和用户空间需要有一定的理解,这里复习下。

操作系统(特指Unix)的内存主要包括以下程序:

  1. 负责响应中断的中断服务程序
  2. 负责管理多个进程从而分享处理器时间的调度程序
  3. 负责管理进程地址空间的内存管理程序
  4. 网络、进程间等系统服务程序

内核拥有受保护的内存空间和访问硬件设备的所有权限。

Linux Kernel.drawio.png

每个处理器在任何指定时间点上的活动必然包括以下三者之一:

  1. 运行于用户空间,执行用户进程。
  2. 运行于内核空间,处理进程上下文,代表某个特定的进程执行。
  3. 运行于内核空间,处理中断上下文,与任何进程无关,处理某个特定的中断。

Unix的发展历史

BPF是Berkeley实验室设计和实现的,说到Berkeley就一定会想到Berkeley在Unix基础上开发的操作系统BSD,这里回顾下Unix的发展历史。

unix-history.gif

BPF

BPF是在类 Unix 系统上数据链路层的一种原始接口,提供原始链路层封包的收发。比较典型的工具是tcpdumptcpdump就是利用了 BPF 的技术来抓取 Unix 操作系统节点上的网络包。Linux 系统中也沿用了 BPF 的技术。

下图是The BSD Packet Filter: A New Architecture for User-level Packet Capture论文中关于BPF的设计图。

BPF 在数据包过滤上引入了两大革新:

  • 一个新的虚拟机 (VM) 设计,可以有效地工作在基于寄存器结构的 CPU 之上;
  • 应用程序使用缓存只复制与过滤数据包相关的数据,不会复制数据包的所有信息。这样可以最大程度地减少BPF 处理的数据;

BPF设计特点

  1. Kernel中实现了一个虚拟机,用户态程序通过系统调用,把数据包过滤代码载入到个内核态虚拟机中运行,这样就实现了内核态对数据包的过滤。这一块对应图中灰色的大方块,也就是 BPF 的核心。
  2. BPF 模块和网络协议栈代码是相互独立的,BPF 只是通过简单的几个 hook 点,就能从协议栈中抓到数据包。内核网络协议代码变化不影响 BPF 的工作,图中右边的protocol stack方块就是指内核网络协议栈。
  3. Kernel中的 BPF filter 模块使用 buffer 与用户态程序进行通讯,把 filter 的结果返回给用户态程序(例如图中的 network monitor),这样就不会产生内核态与用户态的上下文切换(context switch)。

eBPF

eBPF的全称是Extended Berkeley Packet Filter

eBPF的概念最早源自于BSD操作系统中的BPF(Berkeley Packet Filter),1992 伯克利实验室的一篇论文 The BSD Packet Filter: A New Architecture for User-level Packet Capture。描述了BPF是如何更加高效灵活地从操作系统内核中抓取网络数据包的。

BPF overview.png

BPF 实现的基础上,Linux 在 2014 年内核 3.18 的版本上实现了 eBPF,全名是 Extended BPF,也就是 BPF 的扩展。

eBPF可以将强大的安全性、可见性和网络控制逻辑动态插入 Linux 内核。同时eBPF 允许开发者不侵入的,动态向内核插入代码。CiliumeBPF 的一个封装。eBPF 实现的最初目标是优化处理网络过滤器的内部 BPF 指令集。eBPF 用于提供高性能网络、多集群和多云功能、高级负载平衡、透明加密、广泛的网络安全功能、透明的可观察性。最初仅仅工作的内核中,2014 年 6 月,eBPF 扩展到用户空间,这也成为了 BPF技术的转折点。

eBPF overview.png

PS:从上图可以看出,GO在用户空间原生支持eBPF,也丰富了GO在云原生场景下的应用。

eBPF对BPF的增强点

最初仅仅工作的内核中,2014 年 6 月,eBPF 扩展到用户空间,这也成为了 BPF技术的转折点。

  • 首先,对虚拟机做了增强,扩展了寄存器和指令集的定义,提高了虚拟机的性能,并且可以处理更加复杂的程序。
  • 其次,增加了 eBPF maps,这是一种存储类型,可以保存状态信息,从一个 BPF 事件的处理函数传递给另一个,或者保存一些统计信息,从内核态传递给用户态程序。
  • 最后,eBPF 可以处理更多的内核事件,不再只局限在网络事件上。你可以这样来理解,eBPF 的程序可以在更多内核代码 hook 点上注册了,比如 tracepointskprobes 等。

这些增强点带来的好处:

  • 稳定:有循环次数和代码路径触达限制,保证程序可以固定时间结束。
  • 高效:可以通过JIT方式编译成本地机器码,执行效率高。
  • 安全:验证器会对eBPF程序的可访问函数集合和内存地址有严格限制,不会导致内核Panic。
  • 热加载和热卸载:可以热加载和热卸载eBPF程序,无需重启Linux系统。
  • 内核内置:eBPF自身提供了稳定的API。
  • VM通用引擎足够简洁。
  • 数据与功能分离

map-architecture.png

eBPF与BPF的对比

维度cBPFeBPF
内核版本Linux 2.1.75(1997年)Linux 3.18(2014年)[4.x for kprobe/uprobe/tracepoint/perf-event]
寄存器数目2个:A, X10个: R0–R9, 另外 R10 是一个只读的帧指针
寄存器宽度32位64位
存储16 个内存位: M[0–15]512 字节堆栈,无限制大小的 “map” 存储
限制的内核调用非常有限,仅限于 JIT 特定有限,通过 bpf_call 指令调用
目标事件数据包、 seccomp-BPF数据包、内核函数、用户函数、跟踪点 PMCs 等

用户空间和内核交互

eBPF 分为用户空间程序和内核程序两部分:

  • 用户空间程序负责加载 BPF 字节码至内核,如需要也会负责读取内核回传的统计信息或者事件详情;
  • 内核中的 BPF 字节码负责在内核中执行特定事件,如需要也会将执行的结果通过 maps 或者 perf-event 事件发送至用户空间;

用户空间程序与内核中的 BPF 字节码交互的流程主要如下:

  • 使用 LLVM 或者 GCC 工具将编写的 BPF 代码程序编译成 BPF 字节码;

  • 然后使用加载程序 Loader 将字节码加载至内核;内核使用验证器(verfier) 组件保证执行字节码的安全性,以避免对内核造成灾难,在确认字节码安全后将其加载对应的内核模块执行;BPF 观测技术相关的程序程序类型可能是 kprobes/uprobes/tracepoint/perf_events 中的一个或多个,其中:

    • kprobes:实现内核中动态跟踪。 kprobes 可以跟踪到 Linux 内核中的导出函数入口或返回点,但是不是稳定 ABI 接口,可能会因为内核版本变化导致,导致跟踪失效。
    • uprobes:用户级别的动态跟踪。与 kprobes 类似,只是跟踪用户程序中的函数。
    • tracepoints:内核中静态跟踪。tracepoints 是内核开发人员维护的跟踪点,能够提供稳定的 ABI 接口,但是由于是研发人员维护,数量和场景可能受限。
    • perf_events:定时采样和 PMC
  • 内核中运行的 BPF 字节码程序可以使用两种方式将测量数据回传至用户空间

    • maps 方式可用于将内核中实现的统计摘要信息(比如测量延迟、堆栈信息)等回传至用户空间;
    • perf-event 用于将内核采集的事件实时发送至用户空间,用户空间程序实时读取分析;

eBPF的限制

  • eBPF程序不允许调用任意的内核函数,需要通过 BPF Helper 函数。

  • eBPF程序不允许包含无法到达的指令,防止加载无效代码,延迟程序的终止。

  • eBPF程序中循环次数限制且必须在有限时间内结束。

  • eBPF堆栈大小被限制在MAX_BPF_STACK

    • Root用户的BPF程序,指令数从4096逐步放宽到100万指令。
    • 非Root用户,让被限制到4096.

eBPF的实现原理

Go

5大模块

eBPF在内核主要由5个模块协作:

BPF Verifier(验证器)

确保 eBPF 程序的安全。验证器会将待执行的指令创建为一个有向无环图(DAG),确保程序中不包含不可达指令;接着再模拟指令的执行过程,确保不会执行无效指令,这里通过和个别同学了解到,这里的验证器并无法保证100%的安全,所以对于所有BPF程序,都还需要严格的监控和评审。

BPF JIT

将 eBPF 字节码编译成本地机器指令,以便更高效地在内核中执行。

存储模块

多个 64 位寄存器、一个程序计数器和一个 512 字节的栈组成的存储模块,用于控制eBPF程序的运行,保存栈数据,入参与出参。

BPF Helpers(辅助函数)

eBPF 程序不能调用任意的内核参数,只限于内核模块中列出的 BPF Helper 函数,函数支持列表也随着内核的演进在不断增加。

提供了一系列用于 eBPF 程序与内核其他模块进行交互的函数。这些函数并不是任意一个 eBPF 程序都可以调用的,具体可用的函数集由 BPF 程序类型决定。注意,eBPF里面所有对入参,出参的修改都必须符合BPF规范,除了本地变量的变更,其他变化都应当使用BPF Helpers完成,如果BPF Helpers不支持,则无法修改。

bpftool feature probe

通过以上命令可以看到不同类型的eBPF程序可以运行哪些BPF Helpers。具体方法可以查看源码:github.com/torvalds/li…

img

const struct bpf_func_proto bpf_get_prandom_u32_proto = {
    .func = bpf_user_rnd_u32,
    .gpl_only = false,
    .ret_type = RET_INTEGER,
};

BPF Map & context

用于提供大块的存储,这些存储可被用户空间程序用来进行访问,进而控制 eBPF 程序的运行状态。

bpftool feature probe | grep map_type

通过以上命令可以看到系统支持哪些类型的map。

3个动作

先说下重要的系统调用bpf:

int bpf(int cmd, union bpf_attr *attr, unsigned int size);
  • cmd是指令类型
  • attr是cmd的参数
  • size是参数大小
// 5.11内核
enum bpf_cmd {
BPF_MAP_CREATE, 
BPF_MAP_LOOKUP_ELEM, 
BPF_MAP_UPDATE_ELEM, 
BPF_MAP_DELETE_ELEM,
BPF_MAP_GET_NEXT_KEY,
BPF_PROG_LOAD,
BPF_OBJ_PIN,
BPF_OBJ_GET, 
BPF_PROG_ATTACH,
BPF_PROG_DETACH, 
BPF_PROG_TEST_RUN,
BPF_PROG_GET_NEXT_ID, 
BPF_MAP_GET_NEXT_ID,
BPF_PROG_GET_FD_BY_ID,
BPF_MAP_GET_FD_BY_ID,
BPF_OBJ_GET_INFO_BY_FD,
BPF_PROG_QUERY,
BPF_RAW_TRACEPOINT_OPEN,
BPF_BTF_LOAD,
BPF_BTF_GET_FD_BY_ID,
BPF_TASK_FD_QUERY,
BPF_MAP_LOOKUP_AND_DELETE_ELEM,
BPF_MAP_FREEZE,
BPF_BTF_GET_NEXT_ID,
BPF_MAP_LOOKUP_BATCH,
BPF_MAP_LOOKUP_AND_DELETE_BATCH,
BPF_MAP_UPDATE_BATCH, 
BPF_MAP_DELETE_BATCH, 
BPF_LINK_CREATE,
BPF_LINK_UPDATE,
BPF_LINK_GET_FD_BY_ID,
BPF_LINK_GET_NEXT_ID,
BPF_ENABLE_STATS,
BPF_ITER_CREATE,
BPF_LINK_DETACH,
BPF_PROG_BIND_MAP,
};

最核心的就是PROG,MAP相关的cmd,就是程序加载和映射处理。

程序加载

调用BPF_PROG_LOAD cmd,会将BPF程序加载到内核,但eBPF 程序并不像常规的线程那样,启动后就一直运行在那里,它需要事件触发后才会执行。这些事件包括系统调用、内核跟踪点、内核函数和用户态函数的调用退出、网络事件,等等,所以需要第2个动作。

绑定事件

b.attach_kprobe(event="xxx", fn_name="yyy")

以上就是将特定的事件绑定到特定的BPF函数,实际实现原理如下:

(1)借助 bpf 系统调用,加载 BPF 程序之后,会记住返回的文件描述符;

(2)通过attach操作知道对应函数类型的事件编号;

(3)根据attach的返回值调用 perf_event_open 创建性能监控事件;

(4)通过 ioctl 的 PERF_EVENT_IOC_SET_BPF 命令,将 BPF 程序绑定到性能监控事件。

映射操作

通过MAP相关的cmd,控制MAP增删,然后用户态基于该MAP与内核状态进行交互。

一个简单程序

#include <uapi/linux/ptrace.h>
BPF_PERF_OUTPUT(events);
typedef struct {
    u64     arg1;
    char    arg2;
    char    pad[3];
    float   arg3;
} args_event_t;
inline int get_arguments(struct pt_regs *ctx) {
        void* stackAddr = (void*)ctx->sp;
        args_event_t event = {};
        bpf_probe_read(&event.arg1, sizeof(event.arg1), stackAddr+8);
        bpf_probe_read(&event.arg2, sizeof(event.arg2), stackAddr+16);
        bpf_probe_read(&event.arg3, sizeof(event.arg3), stackAddr+20);
        long tmp = 2021;
        bpf_probe_write_user(stackAddr+8, &tmp, sizeof(tmp));
        events.perf_submit(ctx, &event, sizeof(event));
        return 0;
}

eBPF应用

可以做哪些事情?

  • 系统观测探测、访问控制列表,包括内核态和用户态

    • 绝大部分内核函数,函数入口和出口可以跟踪。如何实现的呢?
    • 内存分配的模块目前不允许跟踪观测。
  • 技术观测和故障演练拓扑,流量控制。

  • 安全应用

基于eBPF实现的开源项目

  • Facebook 高性能 4 层负载均衡器 Katran;

  • IO Visor 项目开源的 BCCBPFTraceKubectl-Trace

    • BCC 提供了更高阶的抽象,可以让用户采用 Python、C++ 和 Lua 等高级语言快速开发 BPF 程序

      • img
    • BPFTrace 采用类似于 awk 语言快速编写 eBPF 程序

      • img
    • Kubectl-Trace 则提供了在 kubernetes 集群中使用 BPF 程序调试的方便操作;

  • CloudFlare 公司开源的 eBPF Exporterbpf-tools

    • eBPF Exporter 将 eBPF 技术与监控 Prometheus 紧密结合起来
    • bpf-tools 可用于网络问题分析和排查;
  • bmc-cache

    • memcache+eBPF,基于eBPF实现在内核层面的缓存。
    • Running memcached with BMC improves throughput by up to 18x compared to the vanilla memcached application.
  • Cilium 为下一代微服务 ServiceMesh 打造了具备API感知和安全高效的容器网络方案;底层主要使用 XDP 和 TC 等相关技术;

    • 实际的开发中对于微服务涉及的更多一些,因此后续小节会着重介绍下Cilium

Cilium

Cilium 是一个开源项目,地址:cilium.io/,旨在为 Kubernetes 集群和其他容器编排平台等云原生环境提供网络、安全性和可观察性。

Cilium is an open source software for providing, securing and observing network connectivity between container workloads - cloud native, and fueled by the revolutionary Kernel technology eBPF.

大体上可以理解为 CiliumService Mesh 打造了具备 API 感知和安全高效的容器网络方案, Cilium的底层正是基于 eBPF 技术实现。

eBPF-based Networking, Observability, SecurityDiagram

参考文献

  1. ebpf.io/
  2. What is eBPF? An Introduction and Deep Dive into the eBPF Technology
  3. zhuanlan.zhihu.com/p/378766217
  4. developer.aliyun.com/article/947…
  5. cloudnative.to/blog/bpf-in…
  6. www.tcpdump.org/papers/bpf-…
  7. Cilium - Linux Native, API-Aware Networking and Security for Containers
  8. www.ebpf.top/post/head_f…
  9. kerneltravel.net/blog/2021/e…