大家好,欢迎来到IT知识分享网。
什么是eBPF?
eBPF(不再是任何东西的首字母缩略词)是一项革命性的技术,起源于 Linux 内核,可以在特权上下文(如操作系统内核)中运行沙盒程序。它用于安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。
从历史上看,由于内核具有监督和控制整个系统的特权能力,操作系统一直是实现可观察性、安全性和网络功能的理想场所。同时,操作系统内核由于其核心作用和对稳定性和安全性的高要求而难以发展。因此,与在操作系统之外实现的功能相比,操作系统级别的创新率传统上较低。
eBPF从根本上改变了这一公式。通过允许在操作系统中运行沙盒程序,应用程序开发人员可以运行 eBPF 程序,以便在运行时向操作系统添加其他功能。然后,操作系统保证安全性和执行效率,就像在实时 (JIT) 编译器和验证引擎的帮助下进行本机编译一样。这导致了一波基于 eBPF 的项目,涵盖了广泛的用例,包括下一代网络、可观测性和安全功能。
如今,eBPF 被广泛用于驱动各种用例:在现代数据中心和云原生环境中提供高性能网络和负载平衡,以低开销提取细粒度的安全可观测性数据,帮助应用程序开发人员跟踪应用程序,为性能故障排除、预防性应用程序和容器运行时安全实施提供见解等等。可能性是无穷无尽的,eBPF解锁的创新才刚刚开始。
什么是 eBPF.io?
eBPF.io 是每个人就eBPF主题进行学习和协作的地方。eBPF是一个开放的社区,每个人都可以参与和分享。无论您是想阅读eBPF的初步介绍,查找进一步的阅读材料,还是迈出成为主要eBPF项目贡献者的第一步,eBPF.io 都会在此过程中为您提供帮助。
eBPF简介
钩子概述
eBPF 程序是事件驱动的,在内核或应用程序通过某个钩点时运行。预定义的钩子包括系统调用、函数进入/退出、内核跟踪点、网络事件和其他几个钩子。
如果不存在满足特定需求的预定义钩子,则可以创建内核探测(kprobe)或用户探测(uprobe)来附加内核或用户应用程序中几乎任何位置的eBPF程序。
eBPF程序是如何编写的?
在很多情况下,eBPF 不是直接使用,而是通过 Cilium、bcc 或 bpftrace 等项目间接使用,这些项目在 eBPF 之上提供抽象,不需要直接编写程序,而是提供指定基于意图的定义的能力,然后使用 eBPF 实现这些定义。
如果不存在更高级别的抽象,则需要直接编写程序。Linux 内核期望 eBPF 程序以字节码的形式加载。虽然可以直接编写字节码,但更常见的开发实践是利用像LLVM这样的编译器套件将伪C代码编译为eBPF字节码。
验证
验证步骤可确保 eBPF 程序可以安全运行。它验证程序是否满足多个条件,例如:
- 加载 eBPF 程序的进程具有所需的功能(权限)。除非启用了非特权 eBPF,否则只有特权进程才能加载 eBPF 程序。
- 该程序不会崩溃或以其他方式损坏系统。
- 程序总是运行到完成(即程序不会永远处于循环中,从而阻止进一步的处理)。
即时编译
即时 (JIT) 编译步骤将程序的通用字节码转换为特定于机器的指令集,以优化程序的执行速度。这使得 eBPF 程序的运行效率与本机编译的内核代码或作为内核模块加载的代码一样高效。
Map
eBPF 程序的一个重要方面是共享收集的信息和存储状态的能力。为此,eBPF程序可以利用eBPF映射的概念在广泛的数据结构中存储和检索数据。eBPF 映射可以从 eBPF 程序访问,也可以通过系统调用从用户空间中的应用程序访问。
以下是支持的映射类型的不完整列表,以便了解数据结构的多样性。对于各种映射类型,共享和每 CPU 变体都可用。
- 哈希表、数组
- LRU(最近最少使用)
- 环形缓冲器
- 堆栈跟踪
- LPM(最长前缀匹配)
- …
帮助程序调用
eBPF 程序不能调用任意内核函数。允许这样做会将 eBPF 程序绑定到特定的内核版本,并使程序的兼容性复杂化。相反,eBPF 程序可以将函数调用到帮助程序函数中,这是内核提供的众所周知且稳定的 API。
可用的帮助程序调用集在不断发展。可用的帮助程序调用示例:
- 生成随机数
- 获取当前时间和日期
- eBPF 地图访问
- 获取进程/c组上下文
- 操作网络数据包和转发逻辑
尾部和函数调用
eBPF 程序可以用尾部和函数调用的概念组合。函数调用允许在 eBPF 程序中定义和调用函数。尾部调用可以调用和执行另一个 eBPF 程序并替换执行上下文,类似于 execve() 系统调用对常规进程的操作方式。
安全
权力越大,责任越大。
eBPF是一项非常强大的技术,现在运行在许多关键软件基础设施组件的核心。在 eBPF 的开发过程中,当 eBPF 被考虑纳入 Linux 内核时,eBPF 的安全性是最关键的方面。eBPF的安全性通过几层确保:
所需权限
除非启用了非特权 eBPF,否则所有打算将 eBPF 程序加载到 Linux 内核的进程都必须在特权模式 (root) 下运行或需要该功能CAP_BPF。这意味着不受信任的程序无法加载 eBPF 程序。
如果启用了非特权 eBPF,则非特权进程可以加载某些 eBPF 程序,这些程序的功能集会减少,并且对内核的访问有限。
验证
如果允许进程加载 eBPF 程序,则所有程序仍会通过 eBPF 验证程序。eBPF验证器确保程序本身的安全性。这意味着,例如:
- 程序经过验证以确保它们始终运行到完成,例如,eBPF 程序可能永远不会永远阻塞或处于循环中。eBPF 程序可能包含所谓的有界循环,但仅当验证者能够确保循环包含保证变为真的退出条件时,该程序才会被接受。
- 程序不得使用任何未初始化的变量或越界访问内存。
- 程序必须符合系统的大小要求。无法加载任意大的 eBPF 程序。
- 程序必须具有有限的复杂性。验证程序将评估所有可能的执行路径,并且必须能够在配置的复杂性上限限制内完成分析。
硬化
成功完成验证后,eBPF 程序将根据程序是从特权进程还是非特权进程加载来运行强化过程。此步骤包括:
- 程序执行保护:保存 eBPF 程序的内核内存受到保护并变为只读。如果出于任何原因,无论是内核错误还是恶意操纵,试图修改 eBPF 程序,内核将崩溃,而不是允许它继续执行损坏/操纵的程序。
- 针对幽灵的缓解措施:在推测下,CPU可能会错误地预测分支,并留下可以通过侧通道提取的可观察到的副作用。举几个例子:eBPF 程序屏蔽内存访问,以便在瞬态指令下将访问重定向到受控区域,验证器还遵循仅在推测执行下可访问的程序路径,并且 JIT 编译器在尾部调用无法转换为直接调用的情况下发出 Retpolines。
- 持续致盲:代码中的所有常量都是盲目的,以防止 JIT 喷洒攻击。这可以防止攻击者将可执行代码作为常量注入,在存在另一个内核错误的情况下,攻击者可以跳转到 eBPF 程序的内存部分以执行代码。
抽象运行时上下文
eBPF 程序不能直接访问任意内核内存。必须通过 eBPF 帮助程序访问对程序上下文之外的数据和数据结构的访问。这保证了一致的数据访问,并使任何此类访问都受制于eBPF程序的权限,例如,如果可以保证修改是安全的,则允许运行的eBPF程序修改某些数据结构的数据。eBPF 程序不能随机修改内核中的数据结构。
为什么选择eBPF?
可编程性的力量
让我们从一个类比开始。你还记得GeoCities吗?20年前,网页过去几乎完全是用静态标记语言(HTML)编写的。网页基本上是一个带有能够显示它的应用程序(浏览器)的文档。看看今天的网页,网页已经成为成熟的应用程序,基于Web的技术已经取代了绝大多数用需要编译的语言编写的应用程序。是什么促成了这种演变?
简短的回答是引入JavaScript的可编程性。它开启了一场巨大的革命,导致浏览器演变成几乎独立的操作系统。
为什么会发生这种演变?程序员不再局限于运行特定浏览器版本的用户。必要构建块的可用性并没有说服标准机构需要一个新的 HTML 标记,而是将底层浏览器的创新步伐与运行在顶部的应用程序分离。这当然有点过于简单,因为HTML确实随着时间的推移而发展并为成功做出了贡献,但HTML本身的发展是不够的。
在举这个例子并将其应用于 eBPF 之前,让我们看一下在引入 JavaScript 时至关重要的几个关键方面:
- 安全:不受信任的代码在用户的浏览器中运行。这是通过沙盒化JavaScript程序和抽象对浏览器数据的访问来解决的。
- 持续交付:程序逻辑的演变必须是可能的,而不需要不断发布新的浏览器版本。这是通过提供足以构建任意逻辑的正确低级构建块来解决的。
- 性能:必须以最小的开销提供可编程性。通过引入即时 (JIT) 编译器解决了这个问题。
对于上述所有内容,出于同样的原因,可以在eBPF中找到确切的对应部分。
eBPF对Linux内核的影响
现在让我们回到eBPF。为了理解 eBPF 对 Linux 内核的可编程性影响,它有助于对 Linux 内核的架构以及它如何与应用程序和硬件交互有一个高层次的了解。
Linux 内核的主要目的是抽象硬件或虚拟硬件,并提供一致的 API(系统调用),允许应用程序运行和共享资源。为了实现这一目标,需要维护一组广泛的子系统和层来分配这些职责。每个子系统通常允许进行某种级别的配置,以满足用户的不同需求。如果无法配置所需的行为,则需要更改内核,从历史上看,留下两个选项:
使用 eBPF,可以使用一个新选项,允许重新编程 Linux 内核的行为,而无需更改内核源代码或加载内核模块。在许多方面,这与JavaScript和其他脚本语言如何解锁系统的发展非常相似,这些系统已经变得难以改变或成本高昂。
开发工具链
有几个开发工具链可以帮助开发和管理eBPF程序。它们都满足了用户的不同需求:
BCC
BCC是一个框架,使用户能够编写嵌入其中的eBPF程序的python程序。该框架主要针对涉及应用程序和系统分析/跟踪的用例,其中eBPF程序用于收集统计信息或生成事件,用户空间中的对应程序收集数据并以人类可读的形式显示。运行python程序将生成eBPF字节码并将其加载到内核中。
bpftrace
bpftrace 是 Linux eBPF 的高级跟踪语言,在最近的 Linux 内核 (4.x) 中可用。bpftrace 使用 LLVM 作为后端将脚本编译为 eBPF 字节码,并利用 BCC 与 Linux eBPF 子系统以及现有的 Linux 跟踪功能进行交互:内核动态跟踪 (kprobes)、用户级动态跟踪 (uprobes) 和跟踪点。bpftrace 语言的灵感来自 awk、C 和前身的跟踪器,如 DTrace 和 SystemTap。
eBPF Go 库
eBPF Go库提供了一个通用的eBPF库,它将获取eBPF字节码的过程与eBPF程序的加载和管理解耦。eBPF程序通常是通过编写更高级的语言,然后使用clang / LLVM编译器编译为eBPF字节码来创建的。
libbpf C/C++ Library
libbpf 库是一个基于 C/C++ 的通用 eBPF 库,它有助于将 clang/LLVM 编译器生成的 eBPF 对象文件加载到内核中解耦,并且通常通过为应用程序提供易于使用的库 API 来抽象与 BPF 系统调用的交互。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/108238.html