2021 年 6 月 2 日 林 · 克拉克
17 | 18 | 浏览器中 JavaScript 的运行速度相较于20年前快了很多倍。那是因为浏览器服务厂商花费了很多时间去做了相关方面密集的性能优化。 19 | 20 | 今天,我们开始为完全不同的环境优化 JavaScript 的性能,并在这些环境中应用不同的规则。因为有了WebAssembly,这是可能的! 21 | 22 | 这里我们应该明白 —— 如果你在浏览器中运行 JavaScript ,简单的部署 JS 仍然是最有意义的。浏览器中的 JS 引擎经过了高度调优,以运行交付给它们的 JS 。 23 | 24 | 但是,如果你在 Serverless function 中运行 JS 呢?又或者,如果你想将 JS 运行到像 iOS 或游戏机那样不允许一般 JIT( just-in-time ) 编译环境中呢? 25 | 26 | 对于这些用例,你需要去关注 JS 优化的新浪潮。并且,这项工作也可以服务于其他编程语言模型,例如Python , Ruby 和 Lua,它们也想在这些环境中快速运行。 27 | 28 | 但是,在我们探索这种方法如何快速运行之前,我们需要先了解 WebAssembly 的基本工作原理。 29 | 30 | ## WebAssembly 如何工作? 31 | 32 | 每当您运行 JS 时, JS 源代码都需要以一种或其他方式作为机器代码去执行。这是由 JS 引擎使用解释器和 JIT 编译器等多种技术完成的。(更多详细信息,请参阅[即时( JIT )编译器中的速成课程](https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/)) 33 | 34 |  35 | 36 | 但是,如果您的目标平台没有 JS 引擎怎么办呢?那么你需要在你的代码中部署一个 JS 引擎。 37 | 38 | 为了做到这一点,我们把 JS 引擎部署成为一个 WebAssembly 模块,这让 JS 引擎可以跨越不同类型的计算机体系结构进行移植。并且有了 WASI ,我们也可以让 JS 引擎在不同的操作系统之间进行移植。 39 | 40 | 这意味着整个 JS 环境都将绑定到这个 WebAssembly 实例中。一旦部署了这一实例,你需要做的就是输入 JS 代码,然后实例就会运行该 JS 代码。 41 | 42 |  43 | 44 | JS 引擎不是直接在机器的内存中工作,而是将从字节码到字节码正在操纵 GCed 对象在内的所有内容都放在 Wasm 模块的线性内存中。 45 | 46 |  47 | 48 | 我们使用了 Firefox 的 SpiderMonkey 作为 JS 引擎。SpiderMonkey 是具有业界实力的 JS 虚拟机之一,并经过了浏览器的 “ 实战演练 ”。当你运行或者处理不受信任的输入代码时,这种针对安全的 ” 实战演练 “ 和投入很重要。 49 | 50 | SpiderMonkey 还使用了一种精确堆栈扫描的技术,这对于我在下面解释一些优化十分重要。因为来自于三个不同的组织(Fastly,Mozilla & Igalia)的 BA 成员在为此进行通力协作,SpiderMonkey 还有一个非常容易上手的代码库,这也很重要 51 | 52 | 到目前为止,我所描述的方法没有任何革命性!人们已经这样使用 WebAssembly 运行了 JS 很多年了。 53 | 54 | 但问题是这样很慢, WebAssembly 不允许你动态生成新的机器代码并在纯 WebAssembly 代码中去运行它。这意味着你不可以使用 JIT 。你只可以使用解释器。 55 | 56 | 鉴于此种约束,你可能会发问...... 57 | 58 | ## 但你为何这样做? 59 | 60 | 因为 JIT 是浏览器让 JS 快速运行的方式(并且由于你不能够在 WebAssembly 模块内部进行 JIT 编译),因此这样处理似乎违反常识。 61 | 62 |  63 | 64 | 但是,即便这样,如果我们能够让 JS 运行的更快呢? 65 | 66 | 让我们去看几个用例,在这些用例中,这种方法的快速版本可能真的有用。 67 | 68 | ## 在 iOS 上运行 JS (和其他 JIT 限制环境) 69 | 70 | 出于安全方面的考虑,有些场景不能使用 JIT —— 例如,非特权的 iOS 应用程序和一些智能电视 & 游戏机。 71 | 72 |  73 | 74 | 在这些平台上,你必须使用解释器。但是,在这些平台上长时间运行着各种应用,并且他们需要大量的代码......而这些正是过去你不想使用解释器的情形,因为会减慢执行。 75 | 76 | 如果我们的方法更快,那么这些开发者就可以在无 JIT 平台上使用 JS,且不会对性能造成巨大影响。 77 | 78 | ## Serverless 瞬间冷启动 79 | 80 | 在其他地方,JIT 不是问题,但启动时间是问题,比如在 Serverless functions 下。这就是所谓的冷启动延迟问题。 81 | 82 |  83 | 84 | 即使你使用的是最合适的 JS 环境 —— 一个只启动裸 JS 引擎的隔离服务 —— 你也会看到至少大约5ms的启动延迟。这甚至不包含初始化应用所需要的时间。 85 | 86 | 有一些方法可以隐藏传入请求的启动延迟。但是随着连接时间在网络层中得到优化(例如 QUIC 等提案),隐藏这一点变得更困难。当你把多个 Serverless functions 链接到一起时,将更难隐藏这一点。 87 | 88 | 使用这些技术延迟的平台也经常在请求之间复用实例。在一些情况下,这意味着可以在不同请求之间观察到全局状态,这是一个安全隐患。 89 | 90 | 因为这种冷启动问题,开发者经常不遵循最佳实践。他们将大量 functions 放入一个 Serverless deployment。这导致了另一个安全问题 —— 更大的威胁攻击面。如果 Serverless deployment 的一部分弱点被利用,攻击者就可以访问该 deployment 的中的所有内容。 91 | 92 |  93 | 94 | 但是如果我们可以在这些环境下使 JS 启动时间足够低,那我们是不需要用任何技巧来隐藏启动延迟。我们可以在几微妙内启动一个实例。 95 | 96 | 有了这个,我们可以为每一个请求提供一个实例,这意味着请求之间没有状态。 97 | 98 | 并且因为实例非常轻量,开发者们能够把他们的代码拆分成细粒度的代码片段,将任何单代码片段的威胁攻击面降至最低。 99 | 100 |  101 | 102 | 这种方法还有另一个安全优势。除了轻量和细粒度的代码隔离之外, WebAssembly 引擎提供的安全边界更加可靠。 103 | 104 | 由于用于创建隔离的 JS 引擎是大型代码库,包含了大量进行超复杂优化的底级代码,因此很容易引入漏洞,使攻击者能够逃脱 VM(虚拟机)并访问 VM 运行的系统。这就是 [Chrome](https://www.chromium.org/Home/chromium-security/site-isolation) 和 [Firefox](https://blog.mozilla.org/security/2021/05/18/introducing-site-isolation-in-firefox/) 等浏览器竭尽全力确保网站在完全分离的进程中运行的原因。 105 | 106 | 相比之下, WebAssembly 引擎需要的代码要少的多,因此更容易代码审计,而且有许多代码使用 Rust(一种内存安全的编程语言) 编写的。那么[可以验证](http://cseweb.ucsd.edu/~dstefan/pubs/johnson:2021:veriwasm.pdf)从 WebAssembly 模块生成的本机内存隔离的二进制文件。 107 | 108 | 通过 WebAssembly 引擎内部运行 JS 引擎,我们将这个更安全的外部沙箱边界作为另一道防线。 109 | 110 | 因此,对于这些用例,在 WebAssembly 上快速使用 JS 有很大的好处。但是我们该怎么做呢? 111 | 112 | 为了解决这个问题,我们需要了解 JS 引擎把时间消耗在了哪里。 113 | 114 | ## JS 引擎时间花费在了两个地方 115 | 116 | 我们可以将 JS 引擎所做的工作大致分为两部分:初始化 & 运行时。 117 | 118 | 我认为 JS 引擎是一个承包商。这个承包商被保留来完成一项工作:运行 JS 代码并获得运行结果。 119 | 120 |  121 | 122 | ## 初始化阶段 123 | 124 | 在这个承包商真正开始运行项目之前,需要准备一些初步工作。初始化阶段包括开始执行时只需要发生一次的每一件事情。 125 | 126 | ### 应用初始化 127 | 128 | 对于任何项目,浏览器都需要查看客户端希望其完成的工作,并且设置完成该任务所需要的的资源。 129 | 130 | 例如,浏览器通读项目简报和其他支持文档,并将其转化为可以处理的内容,比如建立一个项目管理系统,包括其中存储和组织的所有文档。 131 | 132 |  133 | 134 | 在 JS 引擎的情况下,这项工作看起来跟像是通读源代码的顶层并将 functions 解析成字节码,为已声明的变量分配内存,并在已定义的地方设置值。 135 | 136 | ### 引擎初始化 137 | 138 | 在某些环境下,例如 Serverless,初始化还有另一部分发生在每个应用初始化之前。 139 | 140 | 这是引擎初始化。首先需要启动 JS 引擎本身,并需要在环境中添加内置函数。 141 | 142 | 我认为这就像在开始工作之前设置办公室 —— 做些像组装 IKEA 椅子和桌子等的事情。 143 | 144 |  145 | 146 | 这可能需要相当长的时间,并且是 Serverless 用例冷启动问题的一部分。 147 | 148 | ## 运行阶段 149 | 150 | 一旦完成初始化阶段,JS 引擎就开始运行代码了。 151 | 152 |  153 | 154 | 这部分工作的速度被称为吞吐量,吞吐量受许多不同的因素的影响。例如: 155 | 156 | - 使用了哪些语言特性 157 | - 从 JS 引擎角度来看,代码行为是否可以预测 158 | - 使用什么样的数据结构 159 | - 代码运行时间是否足够受益于 JS 引擎的优化编译器 160 | 161 | 这是 JS 引擎花费时间的两个阶段。 162 | 163 |  164 | 165 | 如何让这两个阶段的工作更快? 166 | 167 | ## 大幅减少初始化时间 168 | 169 | 我们首先使用名为 [Wizer](https://github.com/bytecodealliance/wizer) 的工具快速进行初始化。下面我将补充 Wizer 如何运行,但对于那些不耐烦的人,你可以理解这是我们在运行一个非常简单的 JS 应用时看到的加速。 170 | 171 |  172 | 173 | 使用 Wizer 运行这个小应用程序时,只需要 0.36 毫秒(或 360 微秒)。这比我们对 JS 隔离方法所期望的速度快 13 倍以上。 174 | 175 | 我们使用称为快照的东西来获得这种快速启动。Nick Fitzgerald 在他[关于 Wizer 的 WebAssembly 峰会演讲](https://youtu.be/C6pcWRpHWG0?t=1338)中更详细地解释了所有这些。 176 | 177 | 那么这是如何工作的呢?在部署代码之前,作为构建步骤的一部分,我们使用 JS 引擎运行 JS 代码直到初始化结束。 178 | 179 | 至此,JS 引擎已经解析了所有的 JS,并将其转化为字节码,并且由 JS 引擎模块存储在线性内存中。引擎在这个阶段也做了大量的内存分配和初始化。 180 | 181 | 因为这个线性内存是如此独立,一旦所有的值都被填满,我们就可以把内存作为数据部分附加到一个 Wasm ( WebAssembly )模块上。 182 | 183 | 当 JS 引擎模块被实例化时, JS 引擎可以访问数据部分中的所有数据。每当引擎需要一点内存时,就可以将引擎需要的部分(或者更确切地说,内存页)复制到自己的线性内存中。有了这个,JS 引擎在启动时不需要做任何设置。所有预初始化的值都已准备就绪。 184 | 185 |  186 | 187 | 目前,我们将此数据部分附加到与 JS 引擎相同的模块。但是未来,一旦[模块链接](https://github.com/WebAssembly/module-linking/blob/master/proposals/module-linking/Explainer.md)到位,我们将能够将数据部分作为单独的模块发布,从而允许 JS 引擎模块被许多不同的 JS 应用程序复用。 188 | 189 | 这提供了较低耦合度。 190 | 191 | JS 引擎模块只包含引擎相关代码。这意味着一旦它被编译,该代码就可以被有效地缓存并在许多不同的实例之间复用。 192 | 193 | 另一方面,特定于应用程序的模块不包含 Wasm( WebAssembly ) 代码。它只包含线性内存,而后者又包含 JS 字节码,以及已初始化的其余 JS 引擎状态。这使得移动该内存并将其发送到任何需要去的地方变得非常容易。 194 | 195 |  196 | 197 | 这有点像 JS 引擎承包商,甚至根本不需要设立办公室。JS 引擎只会收到一个旅行箱。那个旅行箱有整个办公室,里面有所有的东西。所有的设置都准备好, JS 引擎开始直接工作。 198 | 199 |  200 | 201 | 最酷的是它不依赖于 JS ——它只是使用 WebAssembly 的现有属性。因此,您也可以在 Python、Ruby、Lua 或其他运行时中使用相同的技术。 202 | 203 | ## 下一步:提高吞吐量 204 | 205 | 因此,通过这种方法,我们可以获得超快的启动时间。但是吞吐量呢? 206 | 207 | 对于某些用例,吞吐量实际上还不错。如果你有一段很短的 JavaScript 代码,它无论如何都不会通过 JIT——它会一直留在解释器中。因此,在这种情况下,吞吐量将与浏览器中的吞吐量大致相同,并且在传统 JS 引擎完成初始化之前完成。 208 | 209 | 但是对于需要更长时间运行的 JS,JIT 开始运行并不需要很长时间。一旦发生这种情况,吞吐量差异就开始变得明显。 210 | 211 | 正如我上面所说,目前不可能在纯 WebAssembly 中 JIT 编译代码。但事实证明,我们可以将 JIT 附带的一些想法应用到提前编译模型中。 212 | 213 | ### 快速 AOT 编译的 JS(无需分析) 214 | 215 | JIT 使用的一种优化技术是内联缓存。使用内联缓存,JIT 创建一个存根链表,其中包含过去运行一些 JS 字节码的所有方式的快速机器代码路径。(有关更多详细信息,请参阅[实时 (JIT) 编译器中的速成课程](https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/)。) 216 | 217 |  218 | 219 | 您需要列表的原因是因为 JS 中的动态类型。每次代码行使用不同的类型时,您都需要生成一个新的存根并将其添加到列表中。但是如果您之前遇到过这种类型,那么您可以只使用已经为它生成的存根。 220 | 221 | 由于内联缓存 (IC) 通常用于 JIT,人们认为它们非常动态且特定于每个程序。但事实证明,它们也可以应用于 AOT 上下文中。 222 | 223 | 甚至在我们看到 JS 代码之前,我们就已经知道我们将需要生成的许多 IC 存根。那是因为 JS 中有一些模式被大量使用。 224 | 225 | 一个很好的例子是访问对象的属性。这在 JS 代码中经常发生,并且可以通过使用 IC 存根来加速。对于具有特定“形状”或“隐藏类”(即以相同方式布置的属性)的对象,当您从这些对象获取特定属性时,该属性将始终处于相同的偏移量。 226 | 227 | 传统上,JIT 中的这种 IC 存根将硬编码两个值:指向形状的指针和属性的偏移量。这需要我们事先没有的信息。但是我们可以做的是参数化 IC 存根。我们可以将形状和属性偏移量视为传入存根的变量。 228 | 229 | 这样,我们可以创建一个从内存加载值的单个存根,然后在任何地方使用相同的存根代码。我们可以将这些常见模式的所有存根烘焙到 AOT 编译模块中,而不管 JS 代码实际做什么。即使在浏览器设置中,这种 IC 共享也是有益的,因为它让 JS 引擎生成更少的机器代码,改善启动时间和指令缓存局部性。 230 | 231 | 但对于我们的用例来说,这尤其重要。这意味着我们可以将这些常见模式的所有存根烘焙到 AOT 编译模块中,而不管 JS 代码实际做什么。 232 | 233 | 我们发现,只需几 KB 的 IC 存根,我们就可以覆盖绝大多数 JS 代码。例如,使用 2 KB 的 IC 存根,我们可以覆盖 Google Octane 基准测试中 95% 的 JS。从初步测试来看,这个百分比似乎也适用于一般的网络浏览。 234 | 235 |  236 | 237 | 因此,使用这种优化,我们应该能够达到与早期 JIT 相当的吞吐量。一旦我们完成了这项工作,我们将添加更多细粒度的优化并提高性能,就像浏览器厂商的 JS 引擎团队对他们早期的 JIT 所做的那样。 238 | 239 | ### 下一步:也许添加一些分析? 240 | 241 | 我们可以做到 AOT(ahead-of-time)一样,而无需程序知道执行什么以及使用的类型。 242 | 243 | 但是,如果我们可以访问 JIT 拥有的相同类型的分析信息呢?然后我们可以完全优化代码。 244 | 245 | 但是这里有一个问题——开发者通常很难分析他们自己的代码,很难提出具有代表性的示例工作负载。所以我们不确定是否可以获得正确的分析数据。 246 | 247 | 不过,如果我们能找到一种方法来放置好的工具进行分析,那么我们就有可能使 JS 的运行速度几乎与今天的 JIT 一样快(而且无需预热时间!) 248 | 249 | ## 今天如何开始 250 | 251 | 我们对这种新方法感到兴奋,并期待看到可以将其推进多远。我们也很高兴看到其他动态类型语言以这种方式进入 WebAssembly。 252 | 253 | 那么今天就介绍几种入门方法,有什么问题可以在[Zulip](https://bytecodealliance.zulipchat.com/)中提问。 254 | 255 | ### 对于其他想要支持 JS 的平台 256 | 257 | 要在自己的平台上运行 JS,需要嵌入一个支持 WASI 的 WebAssembly 引擎。我们为此使用了[Wasmtime](https://github.com/bytecodealliance/wasmtime)。 258 | 259 | 那么你需要你的 JS 引擎。作为这项工作的一部分,我们添加了对 Mozilla 构建系统的全面支持,用于将 SpiderMonkey 编译为 WASI。Mozilla 即将为 SpiderMonkey 添加 WASI 构建到用于构建和测试 Firefox 的相同 CI 设置。这使得 WASI 成为 SpiderMonkey 的生产质量目标,并确保 WASI 构建随着时间的推移继续工作。这意味着您可以像我们在这里一样[使用 SpiderMonkey](https://spidermonkey.dev/)。 260 | 261 | 最后,您需要用户带上他们预先初始化的 JS。为了解决这个问题,我们还开源了[Wizer](https://github.com/bytecodealliance/wizer),您可以将其[集成到构建工具中](https://github.com/bytecodealliance/wizer#using-wizer-as-a-library),该工具生成特定于应用程序的 WebAssembly 模块,该模块将填充 JS 引擎模块的预初始化内存。 262 | 263 | ### 对于想要使用这种方法的其他语言 264 | 265 | 如果您是 Python、Ruby、Lua 或其他语言的语言社区的一员,你也可以为你的语言构建一个版本。 266 | 267 | 首先,您需要将运行时编译为 WebAssembly,使用 WASI 进行系统调用,就像我们对 SpiderMonkey 所做的那样。然后,为了通过快照获得快速启动时间,您可以[将 Wizer 集成到构建工具中](https://github.com/bytecodealliance/wizer#using-wizer-as-a-library)以生成内存快照。 268 | 269 | -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/talk-stub-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/talk-stub-coverage.png -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-1-introduction/eBPF-flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-1-introduction/eBPF-flowchart.png -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-1-introduction/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: "https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/" 3 | author: "Adrian Ratiu" 4 | translator: "DavadDi" 5 | reviewer: ["yangchuansheng“] 6 | title: "eBPF 概述:第 1 部分:介绍" 7 | summary: "有兴趣了解更多关于 eBPF 技术的底层细节?那么请继续移步,我们将深入研究 eBPF 的底层细节,从其虚拟机机制和工具,到在远程资源受限的嵌入式设备上运行跟踪。" 8 | categories: "译文" 9 | tags: ["ebpf","overview"] 10 | originalPublishDate: 2019-04-05 11 | publishDate: 2021-09-03 12 | --- 13 | 14 | 15 | 16 | ## 1. 前言 17 | 18 | **有兴趣了解更多关于 eBPF 技术的底层细节?那么请继续移步,我们将深入研究 eBPF 的底层细节,从其虚拟机机制和工具,到在远程资源受限的嵌入式设备上运行跟踪。** 19 | 20 | 注意:本系列博客文章将集中在 eBPF 技术,因此对于我们来讲,文中 BPF 和 eBPF 等同,可相互使用。BPF 名字/缩写已经没有太大的意义,因为这个项目的发展远远超出了它最初的范围。BPF 和 eBPF 在该系列中会交替使用。 21 | 22 | - [第 1 部分](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)和[第 2 部分](https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/) 为新人或那些希望通过深入了解 eBPF 技术栈的底层技术来进一步了解 eBPF 技术的人提供了深入介绍。 23 | - [第 3 部分](https://www.collabora.com/news-and-blog/blog/2019/04/26/an-ebpf-overview-part-3-walking-up-the-software-stack/)是对用户空间工具的概述,旨在提高生产力,建立在第 1 部分和第 2 部分中介绍的底层虚拟机机制之上。 24 | - [第 4 部分](https://www.collabora.com/news-and-blog/blog/2019/05/06/an-ebpf-overview-part-4-working-with-embedded-systems/)侧重于在资源有限的嵌入式系统上运行 eBPF 程序,在嵌入式系统中完整的工具链技术栈(BCC/LLVM/python 等)是不可行的。我们将使用占用资源较小的嵌入式工具在 32 位 ARM 上交叉编译和运行 eBPF 程序。只对该部分感兴趣的读者可选择跳过其他部分。 25 | - [第 5 部分](https://www.collabora.com/news-and-blog/blog/2019/05/14/an-ebpf-overview-part-5-tracing-user-processes/)是关于用户空间追踪。到目前为止,我们的努力都集中在内核追踪上,所以是时候我们关注一下用户进程了。 26 | 27 | 如有疑问时,可使用该流程图: 28 | 29 |  30 | 31 | ## 2. eBPF 是什么? 32 | 33 | eBPF 是一个基于寄存器的虚拟机,使用自定义的 64 位 RISC 指令集,能够在 Linux 内核内运行即时本地编译的 "BPF 程序",并能访问内核功能和内存的一个子集。这是一个完整的虚拟机实现,不要与基于内核的虚拟机(KVM)相混淆,后者是一个模块,目的是使 Linux 能够作为其他虚拟机的管理程序。eBPF 也是主线内核的一部分,所以它不像其他框架那样需要任何第三方模块([LTTng](https://lttng.org/docs/v2.10/#doc-lttng-modules) 或 [SystemTap](https://kernelnewbies.org/SystemTap)),而且几乎所有的 Linux 发行版都默认启用。熟悉 DTrace 的读者可能会发现 [DTrace/BPFtrace 对比](http://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.html)非常有用。 34 | 35 | 在内核内运行一个完整的虚拟机主要是考虑便利和安全。虽然 eBPF 程序所做的操作都可以通过正常的内核模块来处理,但直接的内核编程是一件非常危险的事情 - 这可能会导致系统锁定、内存损坏和进程崩溃,从而导致安全漏洞和其他意外的效果,特别是在生产设备上(eBPF 经常被用来检查生产中的系统),所以通过一个安全的虚拟机运行本地 JIT 编译的快速内核代码对于安全监控和沙盒、网络过滤、程序跟踪、性能分析和调试都是非常有价值的。部分简单的样例可以在这篇优秀的 [eBPF 参考](http://www.brendangregg.com/ebpf.html)中找到。 36 | 37 | 基于设计,eBPF 虚拟机和其程序有意地设计为**不是**图灵完备的:即不允许有循环(正在进行的工作是支持有界循环【译者注:已经支持有界循环,\#pragma unroll 指令】),所以每个 eBPF 程序都需要保证完成而不会被挂起、所有的内存访问都是有界和类型检查的(包括寄存器,一个 MOV 指令可以改变一个寄存器的类型)、不能包含空解引用、一个程序必须最多拥有 BPF_MAXINSNS 指令(默认 4096)、"主"函数需要一个参数(context)等等。当 eBPF 程序被加载到内核中,其指令被验证模块解析为有向环状图,上述的限制使得正确性可以得到简单而快速的验证。 38 | 39 | > 译者注: BPF_MAXINSNS 这个限制已经被放宽至 100 万条指令(BPF_COMPLEXITY_LIMIT_INSNS),但是非特权执行的 BPF 程序这个限制仍然会保留。 40 | 41 | 历史上,eBPF (cBPF) 虚拟机只在内核中可用,用于过滤网络数据包,与用户空间程序没有交互,因此被称为 "伯克利数据包过滤器"【译者注:早期的 BPF 实现被称为经典 cBPF】。从内核 v3.18(2014 年)开始,该虚拟机也通过 [bpf() syscall](https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf) 和[uapi/linux/bpf.h](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h) 暴露在用户空间,这导致其指令集在当时被冻结,成为公共 ABI,尽管后来仍然可以(并且已经)添加新指令。 42 | 43 | 因为内核内的 eBPF 实现是根据 GPLv2 授权的,它不能轻易地被非 GPL 用户重新分发,所以也有一个替代的 Apache 授权的用户空间 eBPF 虚拟机实现,称为 "uBPF"。撇开法律条文不谈,基于用户空间的实现对于追踪那些需要避免内核-用户空间上下文切换成本的性能关键型应用很有用。 44 | 45 | 46 | 47 | ## 3. eBPF 是怎么工作的? 48 | 49 | eBPF 程序在事件触发时由内核运行,所以可以被看作是一种函数挂钩或事件驱动的编程形式。从用户空间运行按需 eBPF 程序的价值较小,因为所有的按需用户调用已经通过正常的非 VM 内核 API 调用("syscalls")来处理,这里 VM 字节码带来的价值很小。事件可由 kprobes/uprobes、tracepoints、dtrace probes、socket 等产生。这允许在内核和用户进程的指令中钩住(hook)和检查任何函数的内存、拦截文件操作、检查特定的网络数据包等等。一个比较好的参考是 [Linux 内核版本对应的 BPF 功能](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md)。 50 | 51 | 如前所述,事件触发了附加的 eBPF 程序的执行,后续可以将信息保存至 map 和环形缓冲区(ringbuffer)或调用一些特定 API 定义的内核函数的子集。一个 eBPF 程序可以链接到多个事件,不同的 eBPF 程序也可以访问相同的 map 以共享数据。一个被称为 "program array" 的特殊读/写 map 存储了对通过 bpf() 系统调用加载的其他 eBPF 程序的引用,在该 map 中成功的查找则会触发一个跳转,而且并不返回到原来的 eBPF 程序。这种 eBPF 嵌套也有限制,以避免无限的递归循环。 52 | 53 | 运行 eBPF 程序的步骤: 54 | 55 | 1. 用户空间将字节码和程序类型一起发送到内核,程序类型决定了可以访问的内核区域【译者注:主要是 BPF 帮助函数的各种子集】。 56 | 2. 内核在字节码上运行验证器,以确保程序可以安全运行(kernel/bpf/verifier.c)。 57 | 3. 内核将字节码编译为本地代码,并将其插入(或附加到)指定的代码位置。【译者注:如果启用了 JIT 功能,字节码编译为本地代码】。 58 | 4. 插入的代码将数据写入环形缓冲区或通用键值 map。 59 | 5. 用户空间从共享 map 或环形缓冲区中读取结果值。 60 | 61 | map 和环形缓冲区结构是由内核管理的(就像管道和 FIFO 一样),独立于挂载的 eBPF 或访问它们的用户程序。对 map 和环形缓冲区结构的访问是异步的,通过文件描述符和引用计数实现,可确保只要有至少一个程序还在访问,结构就能够存在。加载的 JIT 后代码通常在加载其的用户进程终止时被删除,尽管在某些情况下,它仍然可以在加载进程的生命期之后继续存在。 62 | 63 | 为了方便编写 eBPF 程序和避免进行原始的 bpf()系统调用,内核提供了方便的 [libbpf 库](https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf),包含系统调用函数包装器,如[bpf_load_program](https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf/bpf.c#L214) 和结构定义(如 [bpf_map](https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf/libbpf.c#L157)),在 LGPL 2.1 和 BSD 2-Clause 下双重许可,可以静态链接或作为 DSO。内核代码也提供了一些使用 libbpf 简洁的例子,位于目录 [samples/bpf/](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/) 中。 64 | 65 | 66 | 67 | ## 4. 样例学习 68 | 69 | 内核开发者非常可怜,因为内核是一个独立的项目,因而没有用户空间诸如 Glibc、LLVM、JavaScript 和 WebAssembly 诸如此类的好东西! - 这就是为什么内核中 eBPF 例子中会包含原始字节码或通过 libbpf 加载预组装的字节码文件。我们可以在 [sock_example.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c) 中看到这一点,这是一个简单的用户空间程序,使用 eBPF 来计算环回接口上统计接收到 TCP、UDP 和 ICMP 协议包的数量。 70 | 71 | 我们跳过微不足道的的 [main](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L98) 和 [open_raw_sock](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.h#L13) 函数,而专注于神奇的代码 [test_sock](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L35)。 72 | 73 | ```c 74 | static int test_sock(void) 75 | { 76 | int sock = -1, map_fd, prog_fd, i, key; 77 | long long value = 0, tcp_cnt, udp_cnt, icmp_cnt; 78 | 79 | map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 256, 0); 80 | if (map_fd < 0) {printf("failed to create map'%s'\n", strerror(errno)); 81 | goto cleanup; 82 | } 83 | 84 | struct bpf_insn prog[] = {BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), 85 | BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol) /* R0 = ip->proto */), 86 | BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */ 87 | BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), 88 | BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */ 89 | BPF_LD_MAP_FD(BPF_REG_1, map_fd), 90 | BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), 91 | BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2), 92 | BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */ 93 | BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* xadd r0 += r1 */ 94 | BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */ 95 | BPF_EXIT_INSN(),}; 96 | size_t insns_cnt = sizeof(prog) / sizeof(struct bpf_insn); 97 | 98 | prog_fd = bpf_load_program(BPF_PROG_TYPE_SOCKET_FILTER, prog, insns_cnt, 99 | "GPL", 0, bpf_log_buf, BPF_LOG_BUF_SIZE); 100 | if (prog_fd < 0) {printf("failed to load prog'%s'\n", strerror(errno)); 101 | goto cleanup; 102 | } 103 | 104 | sock = open_raw_sock("lo"); 105 | 106 | if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd)) < 0) {printf("setsockopt %s\n", strerror(errno)); 107 | goto cleanup; 108 | } 109 | ``` 110 | 111 | 首先,通过 libbpf API 创建一个 BPF map,该行为就像一个最大 256 个元素的固定大小的数组。按 [IPROTO_*](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/in.h#L28) 定义的键索引网络协议(2 字节的 word),值代表各自的数据包计数(4 字节大小)。除了数组,eBPF 映射还实现了[其他数据结构类型](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L113),如栈或队列。 112 | 113 | 接下来,eBPF 的字节码指令数组使用方便的[内核宏](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_insn.h)进行定义。在这里,我们不会讨论字节码的细节(这将在第 2 部分描述机器后进行)。更高的层次上,字节码从数据包缓冲区中读取协议字,在 map 中查找,并增加特定的数据包计数。 114 | 115 | 然后 BPF 字节码被加载到内核中,并通过 libbpf 的 bpf_load_program 返回 fd 引用来验证正确/安全。调用指定了 eBPF [程序类型](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L138),这决定了它可以访问哪些内核子集。因为样例是一个 SOCKET_FILTER 类型,因此提供了一个指向当前网络包的参数。最后,eBPF 的字节码通过套接字层被附加到一个特定的原始套接字上,之后在原始套接字上接受到的每一个数据包运行 eBPF 字节码,无论协议如何。 116 | 117 | 剩余的工作就是让用户进程开始轮询共享 map 的数据。 118 | 119 | ```c 120 | for (i = 0; i < 10; i++) { 121 | key = IPPROTO_TCP; 122 | assert(bpf_map_lookup_elem(map_fd, &key, &tcp_cnt) == 0); 123 | 124 | key = IPPROTO_UDP; 125 | assert(bpf_map_lookup_elem(map_fd, &key, &udp_cnt) == 0); 126 | 127 | key = IPPROTO_ICMP; 128 | assert(bpf_map_lookup_elem(map_fd, &key, &icmp_cnt) == 0); 129 | 130 | printf("TCP %lld UDP %lld ICMP %lld packets\n", 131 | tcp_cnt, udp_cnt, icmp_cnt); 132 | sleep(1); 133 | } 134 | } 135 | ``` 136 | 137 | 138 | 139 | ## 5. 总结 140 | 141 | 第 1 部分介绍了 eBPF 的基础知识,我们通过如何加载字节码和与 eBPF 虚拟机通信的例子进行了讲述。由于篇幅限制,编译和运行例子作为留给读者的练习。我们也有意不去分析具体的 eBPF 字节码指令,因为这将是第 2 部分的重点。在我们研究的例子中,用户空间通过 libbpf 直接用 C 语言从内核虚拟机中读取 eBPF map 值(使用 10 次 1 秒的睡眠!),这很笨重,而且容易出错,而且很快就会变得很复杂,所以在第 3 部分,我们将研究更高级别的工具,通过脚本或特定领域的语言自动与虚拟机交互。 142 | 143 | **[继续阅读(eBPF 概述,第 2 部分:机器和字节码)](https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/)...**。 144 | 145 | -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-2-machine-bytecode/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/ 3 | author: Adrian Ratiu 4 | translator: https://github.com/1020973418 5 | reviewer: 6 | title: An eBPF overview, part 2 : Machine & bytecode 7 | summary: eBPF 概述,第 2 部分:机器和字节码 8 | categories: 译文 9 | tags: ["eBPF", "machine", "bytecode"] 10 | originalPublishDate: 2019-04-15 11 | publishDate: 2021-09-06 12 | --- 13 | 14 | # eBPF 概述,第二部分:机器和字节码 15 | 16 |2019 年 4 月 15 日 阿德里安 · 拉蒂乌
17 | 18 | 在我们的第一篇文章中,我们[介绍了 eBPF VM](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)、它刻意的设计限制以及如何从用户空间进程与其交互。如果您还没有阅读它,您可能需要在继续阅读本篇文章之前阅读上一篇文章,因为如果没有适当了解,直接从机器和字节码细节开始学习可能会很困难。如有疑问,请参阅[第一部分](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)开头的流程图。 19 | 20 | 本系列的第二部分更深入地研究了第一部分中研究的 eBPF VM 和程序。掌握这种底层知识不是强制性的,但对于本系列的其余部分来说是非常有用的基础,我们将在其中检查建立在这些机制之上的更高级别的工具。 21 | 22 | ## 虚拟机 23 | eBPF 是一个 RISC 寄存器机,共有[ 11 个 64 位寄存器](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L45),一个程序计数器和一个 512 字节固定大小的堆栈。九个寄存器是通用读写的,一个是只读堆栈指针,程序计数器是隐式的,即我们只能跳转到计数器的某个偏移量。VM 寄存器始终为 64 位宽(即使在 32 位 ARM 处理器内核中运行!)并且如果最高有效的 32 位为零,则支持 32 位子寄存器寻址 - 这将在第四部分在嵌入式设备上交叉编译和运行 eBPF 程序非常有用。 24 | 25 | 这些寄存器是: 26 | | | | 27 | | --- | --- | 28 | | r0:| 存储函数调用和当前程序退出代码的返回值 | 29 | | r1 - r5:| 作为函数调用的参数,在程序开始时 r1 包含 “上下文” 参数指针 | 30 | | r6 - r9:| 这些在内核函数调用之间被保留 | 31 | | r10:| 每个 eBPF 程序512字节堆栈的只读指针 | 32 | | | | 33 | 34 | 在加载时提供的 eBPF [程序类型](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L136)准确地决定了哪些内核函数子集可以调用,以及在程序启动时通过 r1 提供的 “上下文” 参数。r0 中存储的程序退出值的含义也是由程序类型决定的。 35 | 36 | 每个函数调用在寄存器 r1 - r5 中最多可以有 5 个参数;这适用于 eBPF 到 eBPF 和内核函数的调用。寄存器 r1 - r5 只能存储数字或指向堆栈的指针(作为参数传递给函数),从不直接指向任意内存的指针。所有内存访问都必须先将数据加载到 eBPF 堆栈中,然后才能在 eBPF 程序中使用它。此限制有助于 eBPF 验证器,它简化了内存模型以实现更轻松的正确性检查。 37 | 38 | BPF 可访问的内核“辅助”函数由内核核心(不可通过模块扩展)通过类似于定义系统调用的 API 定义,使用 [BPF_CALL_*](https://github.com/torvalds/linux/blob/v4.20/include/linux/filter.h#L441) 宏。[bpf.h](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L420) 试图为所有 BPF 可访问的内核“辅助”函数提供参考。例如 [bpf_trace_printk](https://github.com/torvalds/linux/blob/v4.20/kernel/trace/bpf_trace.c#L163) 的定义使用 BPF_CALL_5 和 5 对类型/参数名称。定义[参数数据类型](https://github.com/torvalds/linux/blob/v4.20/kernel/trace/bpf_trace.c#L276)很重要,因为在每个 eBPF 程序加载时,eBPF 验证器确保寄存器数据类型与被调用方参数类型匹配。 39 | 40 | eBPF 指令也是固定大小的 64 位编码,大约 100 条指令(目前...)分为[ 8 类](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf_common.h#L5)。VM 支持来自通用内存( map 、堆栈、“上下文”如数据包缓冲区等)的 1 - 8 字节加载/存储、向前/向后(非)条件跳转、算术/逻辑运算和函数调用。如需深入了解操作码格式,请参阅 Cilium 项目[指令集文档](https://cilium.readthedocs.io/en/latest/bpf/#instruction-set)。IOVisor 项目还维护了一个有用的[指令规范](https://github.com/iovisor/bpf-docs/blob/master/eBPF.md)。 41 | 42 | 在本系列第一部分研究的示例中,我们使用了一些有用的[内核宏](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_insn.h)来使用以下[结构](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L64)创建 eBPF 字节码指令数组(所有指令都以这种方式编码): 43 | 44 | ```c 45 | struct bpf_insn { 46 | __u8 代码;/* opcode */ 47 | __u8 dst_reg:4; /* dest register */ 48 | __u8 src_reg:4; /* source register */ 49 | __s16 off; /* signed offset */ 50 | __s32 imm; /* signed immediate constant */ 51 | }; 52 | 53 | msb lsb 54 | +------------------------+----------------+----+----+--------+ 55 | |immediate |offset |src |dst |opcode | 56 | +------------------------+----------------+----+----+--------+ 57 | ``` 58 | 让我们看一下 [BPF_JMP_IMM](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_insn.h#L167) 指令,它针对立即值对条件跳转进行编码。下面的宏注释对指令逻辑应该是不言自明的。操作码编码指令类 BPF_JMP 、操作(通过 BPF_OP 位域传递以确保正确性)和表示它是对立即数/常量值 BPF_K 的操作的标志进行编码。 59 | 60 | ```c 61 | #define BPF_OP(code) ((code) & 0xf0) 62 | #define BPF_K 0x00 63 | 64 | /* 针对立即数的条件跳转,if (dst_reg 'op' imm32) goto pc + off16 */ 65 | 66 | #define BPF_JMP_IMM(OP, DST, IMM, OFF) \ 67 | ((struct bpf_insn) { \ 68 | .code = BPF_JMP | BPF_OP(OP) | BPF_K, \ 69 | .dst_reg = DST, \ 70 | .src_reg = 0 71 | , \ 72 | .off = OFF, \ .imm = IMM }) 73 | ``` 74 | 如果我们计算值或反汇编包含 BPF_JMP_IMM ( BPF_JEQ , BPF_REG_0 , 0 , 2 ) 的 eBPF 字节码二进制文件,我们会发现它是 0x020015 格式。这个特定的字节码非常频繁地用于测试存储在 r0 中的函数调用的返回值;如果 r0 == 0,它会跳过接下来的 2 条指令。 75 | 76 | ## 重温我们的字节码 77 | 78 | 现在我们已经掌握了必要的知识来完全理解本系列第一部分中使用的字节码 eBPF 示例,我们将逐步解释它。请记住,[sock_example.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c) 是一个简单的用户态程序,它使用 eBPF 来计算在环回接口上接收到的 TCP、UDP 和 ICMP 协议数据包的数量。 79 | 80 | 在高层次上,代码所做的是从接收到的数据包中读取协议编号,然后将其推送到 eBPF 堆栈上,用作 map_lookup_elem 调用的索引,该调用获取相应协议的数据包计数。map_lookup_elem 函数采用 r0 中的索引(或键)指针和 r1 中的 map 文件描述符。如果查找调用成功,r0 将包含一个指向存储在协议索引处的 map 值的指针。然后我们原子地增加 map 值并退出。 81 | 82 | ```c 83 | BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), 84 | ``` 85 | 当 eBPF 程序启动时,上下文(在这种情况下是数据包缓冲区)由 r1 中的地址指向。r1 将在函数调用期间用作参数,因此我们也将其存储在 r6 中作为备份。 86 | 87 | ```c 88 | BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol) /* R0 = ip->proto */), 89 | ``` 90 | 91 | 该指令将一个字节( BPF_B )从上下文缓冲区(在本例中为网络数据包缓冲区)中的偏移量加载到 r0 中,因此我们提供要加载到 r0 的 [iphdr 结构](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/ip.h#L86)中的协议字节的偏移量。 92 | 93 | ```c 94 | BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */ 95 | ``` 96 | 将包含先前读取的协议的字 ( BPF_W ) 压入堆栈(由 r10 指向,以偏移量 -4 字节开头)。 97 | 98 | ```c 99 | BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), 100 | BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */ 101 | ``` 102 | 103 | 将堆栈地址指针移至 r2 并减去 4,因此现在 r2 指向协议值,用作下一次 map 键查找的参数。 104 | 105 | ```c 106 | BPF_LD_MAP_FD(BPF_REG_1, map_fd), 107 | ``` 108 | 109 | 将本地进程内的文件描述符引用包含协议包计数的 map 到 r1 寄存器。 110 | 111 | ```c 112 | BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), 113 | ``` 114 | 115 | 使用 r2 指向的堆栈中的协议值作为键执行 map 查找调用。结果存储在 r0 中:指向由键索引的值的指针地址。 116 | 117 | ```c 118 | BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2), 119 | ``` 120 | 121 | 还记得 0x020015 格式吗?这与第一部分的字节码相同。如果 map 查找没有成功,则 r0 == 0 所以我们跳过接下来的两条指令。 122 | 123 | ```c 124 | BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */ 125 | BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* xadd r0 += r1 */ 126 | ``` 127 | 128 | 增加 r0 指向的地址处的 map 值。 129 | 130 | ```c 131 | BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */ 132 | BPF_EXIT_INSN(), 133 | ``` 134 | 将 eBPF retcode 设置为 0 并退出。 135 | 136 | 尽管这个 sock_example 逻辑非常简单(它只是在 map 中增加的一些数字),但在原始字节码中实现或理解是困难的。更复杂的任务在像这样的汇编程序中完成时变得极其困难。展望未来,我们将开始使用更高级的语言和工具,以更少的工作开启更强大的 eBPF 用例。 137 | 138 | ## 总结 139 | 140 | 在这一部分中,我们仔细观察了 eBPF 虚拟机的寄存器和指令集,了解了 eBPF 可访问的内核函数是如何从字节码中调用的,以及它们是如何被核心内核通过类似 syscall 的特殊目的 API 定义的。我们也完全理解了第一部分例子中使用的字节码。还有一些未探索的领域,如创建多个 eBPF 程序函数或链式 eBPF 程序以绕过 Linux 发行版的 4096 条指令限制。也许我们会在以后的文章中探讨这些。 141 | 142 | 现在,主要的问题是编写原始字节码很困难的,这非常像编写汇编代码,而且编写效率低下。在第三部分中,我们将开始研究使用高级语言编译成 eBPF 字节码,到此为止我们已经了解了虚拟机工作的底层基础知识。 -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram1.png -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram2.jpg -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram3.jpg -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: "https://www.collabora.com/news-and-blog/blog/2019/04/26/an-ebpf-overview-part-3-walking-up-the-software-stack/" 3 | author: "Adrian Ratiu" 4 | translator: "DavadDi" 5 | reviewer: ["yangchuansheng“] 6 | title: "eBPF 概述:第 3 部分:软件开发生态" 7 | summary: "为了理解这些工具是如何工作的,我们先定义一下 eBPF 程序的高层次组件:后端、加载器、前端和数据结构等。开发方式包括 LLVM eBPF 编译器/BCC/BPFTrace/IOVisor等实现" 8 | categories: "译文" 9 | tags: ["ebpf","overview"] 10 | originalPublishDate: 2019-04-26 11 | publishDate: 2021-09-06 12 | --- 13 | 14 | 15 | 16 | ## 1. 前言 17 | 18 | 在本系列的[第 1 部分](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)和[第 2 部分](https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/)中,我们对 eBPF 虚拟机进行了简洁的深入研究。阅读上述部分并不是理解第 3 部分的必修课,尽管很好地掌握了低级别的基础知识确实有助于更好地理解高级别的工具。为了理解这些工具是如何工作的,我们先定义一下 eBPF 程序的高层次组件: 19 | 20 | - **后端**:这是在内核中加载和运行的 eBPF 字节码。它将数据写入内核 map 和环形缓冲区的**数据结构**中。 21 | - **加载器:**它将字节码**后端**加载到内核中。通常情况下,当加载器进程终止时,字节码会被内核自动卸载。 22 | - **前端:**从**数据结构**中读取数据(由**后端**写入)并将其显示给用户。 23 | - **数据结构**:这些是**后端**和**前端**之间的通信手段。它们是由内核管理的 map 和环形缓冲区,可以通过文件描述符访问,并需要在**后端**被加载之前创建。它们会持续存在,直到没有更多的**后端**或**前端**进行读写操作。 24 | 25 | 在第 1 部分和第 2 部分研究的 [sock_example.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c) 中,所有的组件都被放置在一个 C 文件中,所有的动作都由用户进程完成。 26 | 27 | - [第 40-45 行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L40-L45)创建 map**数据结构**。 28 | - [第 47-61 行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L47-L61)定义**后端**。 29 | - [第 63-76 行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L63-L76)在内核中**加载**后端 30 | - [第 78-91 行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L78-L91)是**前端**,负责将从 map 文件描述符中读取的数据打印给用户。 31 | 32 | eBPF 程序可以更加复杂:多个**后端**可以由一个(或单独的多个!)**加载器**进程加载,写入多个**数据结构**,然后由多个**前端**进程读取,所有这些都可以发生在一个跨越多个进程的用户 eBPF 应用程序中。 33 | 34 |  35 | 36 | 37 | 38 | ## 2. 层级 1:容易编写的后端:LLVM eBPF 编译器 39 | 40 | 我们在前面的文章中看到,在内核中编写原始的 eBPF 字节码是不仅困难而且低效,这非常像用处理器的汇编语言编写程序,所以很自然地开发了一个能够将 LLVM 中间表示编译成 eBPF 程序的模块,并从 2015 年的 v3.7 开始发布(GCC 到现在为止仍然不支持 eBPF)。这使得多种高级语言如 C、Go 或 Rust 的子集可以被编译到 eBPF。最成熟和最流行的是基于 C 语言编写的方式,因为内核也是用 C 写的,这样就更容易复用现有的内核头文件。 41 | 42 | LLVM 将 "受限制的 C" 语言(记住,没有无界循环,最大 4096 条指令等等,见第 1 部分开始)编译成 ELF 对象文件,其中包含特殊区块(section),并可基于 bpf()系统调用,使用 libbpf 等库加载到内核中。这种设计有效地将**后端**定义从**加载器**和**前端**中分离出来,因为 eBPF 字节码包含在 ELF 文件中。 43 | 44 | 内核还在 [samples/bpf/](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/) 下提供了使用这种模式的例子:\*\_kern.c 文件被编译为 \*\_kern.o(**后端**代码),被 \*\_user.c(**装载器**和**前端**)加载。 45 | 46 | 将本系列第 1 和第 2 部分的 [sock_exapmle.c 原始字节码](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L47-L61) 转换为 "受限的 C" 代码“ [sockex1_kern.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L47-L61),这比原始字节码更容易理解和修改。 47 | 48 | ```c 49 | #include2021 年 8 月 16 日 马诺斯 · 萨拉蒂斯
16 | 17 |  18 | 19 | ## eBPF 简介 20 | 当前的 IT 监控软件缺乏系统和应用程序最小停机时间的必要指标度量。大多数 IT 监控软件提供系统和应用程序指标,但要正确监控您的基础架构所需的远不止这些。随着[ eBPF ](https://ebpf.io/)的产生和不断进步,eBPF 技术允许监视软件从 Linux 内核提供了更丰富的监控信息并将其呈现出来。具体而言,eBPF 监控可以更好地了解内部系统究竟发生了什么,这有助于确定基础架构可以改进性能的地方。 21 | 22 | 在 Netdata,我们能够充分利用 eBPF 的能力使您的团队能够: 23 | 24 | + 充分利用 Linux 内核,提供基于从 Linux 内核直接采集出的监控数据,这些数据是前所未有的。 25 | + 通过新的、附加的指标和图表改进了监控范围,这些指标和图表具有关于硬盘、文件系统等方面更完整的信息。 26 | + 基于新的 eBPF 图表,使用一组新的用例补充警报, 27 | + 了解有关 Linux 内核及其隐藏但有实际用途的更多秘密信息。 28 | 29 | 作为基础,我们使用的代码最初是由[ IOvisor ](https://www.iovisor.org/)中的[ BCC ](https://github.com/iovisor/bcc)工具,[ Cloudflare ](https://www.cloudflare.com/)中的[ eBPF exporter ](https://github.com/cloudflare/ebpf_exporter)开发的;我们非常感谢他们的贡献,并打算扩大他们的工作范围,将这些工具的价值分享给你们所有人。 30 | 31 | Netdata 是所有 eBPF 工具的超集。此外,通过消除配置上的开销,我们简化了可视化信息之前的整个过程。Netdata,你可以使用一个命令安装我们的监控代理,把它连接到一个在[ Cloud ](https://app.netdata.cloud/?utm_source=website&utm_content=get_netdata_button1)上的[ Space ](https://learn.netdata.cloud/docs/cloud/spaces),开箱即用的 eBPF 监控,避免了寻找 eBPF工具,需要编译,运行,收集的复杂性和存储输出以及开发用于故障排除的可视化所需的配置。我们为您处理这些所有的复杂性。只需[在您的代理上启用 eBPF ](https://learn.netdata.cloud/docs/agent/collectors/ebpf.plugin),您就可以开始使用了! 32 | 33 | ## 磁盘监控和预警 34 | 35 |  36 | 37 | Netdata 通过在现有的仪表盘上提供的许多开箱即用的图表来监控您的所有磁盘。这些数据包括但不限于: 38 | + 磁盘 I/O 带宽:磁盘的数据流的输入和输出 39 | + 丢弃数据量:从块设备读取、写入或[丢弃](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_storage_devices/discarding-unused-blocks_managing-storage-devices)的扇区数。要配置丢弃,请访问此[文档](https://wiki.gentoo.org/wiki/SSD),获取有关使用丢弃和 Netdata 仪表板的更多信息,您也可以参阅这篇[文章](https://lwn.net/Articles/347511/)以了解更多信息。 40 | + 磁盘完成的 I/O 操作:读取或写入成功完成的总数据量 41 | + 磁盘已完成扩展 I/O 操作: I/O 操作完成后被丢弃的 blocks 数和刷新数 42 | + 磁盘当前 I/O 操作与 I/O:当前正在进行的操作。 43 | + 磁盘积压: 待处理的磁盘操作的等待时间 44 | + 磁盘拥塞时间测量磁盘拥塞的时间 45 | + 磁盘使用时间 46 | + 平均完成 I/O 操作时间成功完成的读取和写入的操作 47 | + 平均完成的扩展 I/O 操作时间与丢弃和刷新时间 48 | + 平均完成的 I/O 操作带宽 49 | + 平均丢弃数据量 50 | + 已完成 I/O 操作的平均服务时间 51 | + 磁盘合并操作作为彼此相邻的读取和写入可以合并以提高效率 52 | + 磁盘合并丢弃操作所有已完成 I/O 的总计的 I/O 时间 53 | + 磁盘总计 I/O 时间每次刷新、丢弃的扩展操作 54 | 55 | 现在,使用 eBPF,您还可以获得以下额外指标: 56 | + 磁盘延迟 ( eBPF ):延迟图表显示直方图以及读取和写入数据所需的时间。 57 | + 同步 ( eBPF ):当数据从内存页面缓存移动到硬盘时。 58 | + 页面缓存 ( eBPF ):数据如何在主机上实时更改。 59 | + 挂载点监控 ( eBPF ):当分区/磁盘被移除并插入到您的主机上时。 60 | 61 | 我们为以下项目提供预建预警: 62 | + 磁盘利用率 63 | + Inode 利用率,您可以在[此处](https://en.wikipedia.org/wiki/Inode)找到有关磁盘 Inode 的更多信息。 64 | + 磁盘积压 65 | + 延迟 66 | 67 | 对于每个挂载点 Netdata 提供: 68 | 69 | 磁盘空间使用 70 | 71 | 磁盘空间利用率。reserved for root 是系统自动预留的,防止 root 用户空间不足。 72 | 73 | 磁盘文件( inode )使用——耗尽可用的 inode(或索引节点)会导致无法创建新文件的问题。 74 | inodes(或索引节点)是文件系统对象(例如文件和目录)。在许多类型的文件系统实现中,inode 的最大数量在文件系统创建时是固定的,限制了文件系统可以容纳的最大文件数量。设备可能会耗尽 inode。发生这种情况时,即使可能有可用空间,也无法在设备上创建新文件。 75 | 76 | 挂载系统调用 ( eBPF ) 77 | + 当系统调用挂载和卸载时,调用 Monitor 。 78 | + 当设备从计算机中插入或移除时,这可能是硬件问题、安全问题。 79 | 80 | 还有一个[ SMART 插件](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin/smartd_log),但我们不建议使用它,因为您的数据可能会受到损害。默认情况下不启用。 81 | 82 | ## 文件系统监控 83 | Netdata 监控不同的文件系统,例如: 84 | + 虚拟文件系统( eBPF ) 85 | + 删除文件 – 监控 Linux 上删除文件的频率; 86 | + 对 IO 的调用 - 计算从硬盘写入(输入)和读取(输出)事件的数量 87 | + 写入和读取的字节数 - 对上一个图表的补充,但为了测量(字节数)调用。 88 | + 调用 vfs_fsync – 监视操作系统何时调用文件同步以将内存中更改的页面存储到磁盘。 89 | + 调用 vfs_open – 调用打开文件 90 | + 调用 vfs_create – 调用创建新文件的函数 91 | + EXT4、XFS、BTRFS、ZFS 延迟( eBPF ) 92 | + 读取请求的延迟以及从 ext4 文件系统读取数据所需的时间。 93 | + 写入请求的延迟以及执行写入事件所需的时间。 94 | + 打开请求的延迟以及执行打开事件所需的时间。 95 | + 同步延迟请求执行同步所需的时间。 96 | + NFS ( eBPF ) 97 | + 读取请求的 NFS 延迟以及执行读取事件所需的时间。 98 | + 写入请求的 NFS 延迟以及执行写入事件所需的时间。 99 | + 打开请求的 NFS 延迟以及执行打开事件所需的时间。 100 | + getattr 请求的 NFS 延迟以及执行 getattr 请求所需的时间。 101 | + 挂载文件系统( eBPF ) 102 | + Netdata 正在监视文件系统何时在操作系统上安装和卸载。 103 | 104 | ## 同步 105 | 106 | + 同步 ( eBPF ):Netdata 监控六个不同的系统调用(fsync 、fdatasync 、msync 、sync 、syncfs 和 syncfilerange ),负责将数据从页面缓存同步到磁盘。 107 | + 页面缓存( eBPF ):Netdata 监控同步到磁盘的页面缓存的访问和更改。 108 | 109 |  110 | 111 |  112 | 我们现在提供更多增强指标用于监控虚拟文件系统 ( [VFS](https://en.wikipedia.org/wiki/Virtual_file_system) ) ,还提供对某些文件系统执行打开文件、在磁盘上写入数据、从磁盘读取数据、删除文件和同步等操作所需的延迟的监控. 113 | 114 | ## eBPF 与 apps.plugin 的集成 115 | 116 | 一些图表显示文件系统和内存部分,并且在启用与 apps.plugin 的集成时也会按应用程序显示。由于这种集成,您可以看到特定应用程序如何使用硬盘。 117 | 118 |  119 | 120 | 应用程序集成 121 | 122 |  123 | 124 | 让我们看一个例子: 125 | 126 | 延迟是完成事件所需的时间。Netdata 计算函数调用与其返回之间的差异。最终结果按时间间隔存储。 127 | 128 | 每个硬盘都有自己的执行读写操作的延迟,要正确设置预警,我们建议您查看硬盘手册。 129 | 130 |  131 | 132 | 为了补充为硬盘提供的信息,Netdata 还监视文件系统上特定操作的延迟。 133 | 134 |  135 | 136 | ## 概括 137 | 感谢您光临并学习如何使用 Netdata 监控您的磁盘和文件系统!要获得丰富的体验,请免费注册[ Netdata Cloud ](https://app.netdata.cloud/sign-in?cloudRoute=spaces?utm_source=email&utm_content=ebpf_blog)(如果您还没有注册)并访问: 138 | + 来自多个节点的指标聚合到一个图表中。为此,只需创建一个帐户,将您的节点连接到一个 Space ,然后转到概览探索您的指标 139 | + 运行[指标关联](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations)等智能功能,将特定指标的使用模式[关联](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations)起来,这些指标在 Netdata 为您的系统收集的所有指标中具有相同的模式 140 | 141 | 最后,请记住[在您的代理上启用 eBPF ](https://learn.netdata.cloud/docs/agent/collectors/ebpf.plugin)!我们为磁盘和文件系统监控提供最全面的实时监控体验。只需轻松的加入监控! 142 | 143 | 如果您是 Netdata 的新手,请务必下载 Agent!如果您已经拥有代理,请注册 Netdata Cloud。 -------------------------------------------------------------------------------- /2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-00.png -------------------------------------------------------------------------------- /2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-01.png -------------------------------------------------------------------------------- /2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-02.png -------------------------------------------------------------------------------- /2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: https://blog.cloudflare.com/programmable-packet-filtering-with-magic-firewall/ 3 | author: Chris J Arges 4 | translator: https://github.com/nevermosby 5 | reviewer: 6 | title: How We Used eBPF to Build Programmable Packet Filtering in Magic Firewall 7 | summary: 我们如何在Magic Firewall中使用eBPF打造可编程的网络包过滤能力 8 | categories: 译文 9 | tags: ["eBPF", "firewall", "packet", "filtering"] 10 | originalPublishDate: 2021-12-06 11 | publishDate: 2021-12-13 12 | --- 13 | 14 | # 我们如何在Magic Firewall中使用eBPF打造可编程的网络包过滤能力 15 |2021 年 12 月 6 日 克里斯 · J · 阿尔杰斯
16 | 17 |  18 | 19 | Cloudflare 作为全球最强大的网络服务提供商之一,我们日复一日地积极保护用户服务免受各种各样的网络攻击。对于使用 Magic Transit 服务的用户来说,Magic Transit 提供的 DDoS 保护功能可以检测和阻止攻击行为,从而保护他们的服务。而我们的 [Magic Firewall](https://www.cloudflare.com/magic-firewall/) 服务则允许数据包级别的自定义规则,使用户能够淘汰防火墙硬件设备,直接在 Cloudflare 网络服务上就可以屏蔽恶意流量。 20 | 21 | 当前网络攻击类型和复杂性不断发展,如最近[针对 VoIP 服务](http://blog.cloudflare.com/update-on-voip-attacks/)的 DDoS 和反射[攻击](http://blog.cloudflare.com/attacks-on-voip-providers/)就表明了这一点,这些攻击针对的是会话初始协议(SIP)协议。对抗这些攻击需要将数据包过滤的极限,已经提升到传统防火墙所能做到的范围之外了。我们通过采用先进一流的技术,并结合新的方式将它们整合起来,使 Magic Firewall 服务成为一种速度极快、完全可编程的防火墙,甚至可以抵御目前最复杂的网络攻击。 22 | 23 | ## 魔力防火墙 24 | [Magic Firewall](http://blog.cloudflare.com/introducing-magic-firewall/) 服务是一个基于 Linux nftables 技术的分布式无状态网络数据包防火墙。它运行在 Cloudflare 全球每个数据中心的每台服务器上。为了提供更好的隔离性和灵活性,每个客户的 nftables 规则都配置在他们自己的 Linux 网络命名空间内。 25 | 26 |  27 | 28 | 这张图展示了使用 Magic Transit 服务时网络数据包的生命周期,Magic Transit 内置了 Magic Firewall 服务。首先,数据包刚进入服务器时,DDoS 保护开始生效,它会尽可能早地阻止攻击行为。接下来,数据包被路由到客户特定的网络命名空间,此时 nftables 规则被作用到数据包上。然后,数据包通过GRE隧道被路由回原始路径。Magic Firewall 用户可以使用灵活的 [Wirefilter 语法](https://github.com/cloudflare/wirefilter),只要通过[单一 API 接口](https://developers.cloudflare.com/magic-firewall)就能构建防火墙声明策略。同时,也可以通过 Cloudflare 仪表板,使用友好的 UI 拖拽元素来配置规则。 29 | 30 | Magic Firewall 服务为匹配各种网络数据包参数,提供了一种非常强大的语法,但它也仅限于 nftables 提供的匹配能力。虽然这对大多数的使用场景来说已是绰绰有余,但它没有提供足够的灵活性,来实现我们想要的高级数据包解析和内容匹配能力。因此,我们需要更多的力量。 31 | 32 | ## 你好 eBPF,Nftables 这厢有礼了 33 | 34 | 为了满足 Linux 网络需求,想要增加更多的功能时,扩展型伯克利包过滤技术([eBPF](https://ebpf.io/))是你的不二选择。有了 eBPF,你可以插入数据包处理程序并 *在内核空间* 执行,这样能让你拥有熟悉的编程范式带来的灵活性,同时兼具内核执行的高效性。Cloudflare [热爱 eBPF ](http://blog.cloudflare.com/tag/ebpf/),这项技术在打造我们自身产品期间,起到了变革作用。因而我们希望找到一种方法,通过 eBPF 来扩展我们在 Magic Firewall 中对nftables的使用场景。这意味着能够在 nftables 表内和链内,使用 eBPF 程序来实现规则匹配。这样做能保留我们现有的基础设施和代码,并进一步扩展它,从而就能实现“鱼和熊掌兼得”。 35 | 36 | 如果 nftables 能够原生利用eBPF的话,这就会变得简单多了。。。唉,我们不得不继续我们的探索,目前已经知道 iptables 可以跟 eBPF 集成,让我们从这里开始入手。例如,可以使用 iptables 和一个持久化eBPF程序来丢弃数据包,命令如下: 37 | 38 | ```bash 39 | iptables -A INPUT -m bpf --object-pinned /sys/fs/bpf/match -j DROP 40 | ``` 41 | 42 | 这条线索帮助我们走上了正确的道路。Iptables 使用 [xt_bpf](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/xt_bpf.c#n60) 扩展模块来对接一个eBPF程序。这个扩展模块使用 **BPF_PROG_TYPE_SOCKET_FILTER** eBPF程序类型,它允许我们在套接字缓冲区(socket buffer)加载数据包信息并根据我们的代码返回值。 43 | 44 | 既然我们知道 iptables 可以使用 eBPF,为什么不直接利用它呢?Magic Firewall 目前使用的还是 nftables,它灵活的语法和可编程的接口,对于我们的用户场景是一个不错的选择。因此,我们需要找到一种方法,实现基于 nftables 使用 **xt_bpf** 扩展模块。 45 | 46 |  47 | 48 | 这张[示意图](https://developers.redhat.com/blog/2020/08/18/iptables-the-two-variants-and-their-relationship-with-nftables#using_iptables_nft) 有助于解释 iptables、nftables 和内核之间的关系。nftables API 可以被iptables 和nft 用户空间程序使用,也可以配置使用xtables 模块(包含 xt_bpf)和一般nftables 模块进行匹配。 49 | 50 | 这意味着,只要使用正确的 API 调用(发送 netlink/netfilter 消息),我们就可以将 xt_bpf 匹配嵌入到 nftables 规则中。为了做到这一点,我们需要了解应该发送哪些 netfilter 信息。通过使用 strace、Wireshark 等工具,尤其当我们使用[源码](https://github.com/torvalds/linux/blob/master/net/netfilter/xt_bpf.c),就能够构建一个消息体,可以针对特定的 nftables 表和链,附加一个 eBPF 规则。 51 | 52 | ```bash 53 | NFTA_RULE_TABLE table 54 | NFTA_RULE_CHAIN chain 55 | NFTA_RULE_EXPRESSIONS | NFTA_MATCH_NAME 56 | NFTA_LIST_ELEM | NLA_F_NESTED 57 | NFTA_EXPR_NAME "match" 58 | NLA_F_NESTED | NFTA_EXPR_DATA 59 | NFTA_MATCH_NAME "bpf" 60 | NFTA_MATCH_REV 1 61 | NFTA_MATCH_INFO ebpf_bytes 62 | ``` 63 | 64 | 为 netlink/netfilter 消息结构体添加eBPF匹配后,应该看起来像上面的例子。当然,这个消息需要正确嵌入,同时当有一个匹配需求时,需要包含一个条件型步骤,就像一次判决。下一步是对 `ebpf_bytes` 格式进行解码,如下例所示: 65 | 66 | ```C 67 | struct xt_bpf_info_v1 { 68 | __u16 mode; 69 | __u16 bpf_program_num_elem; 70 | __s32 fd; 71 | union { 72 | struct sock_filter bpf_program[XT_BPF_MAX_NUM_INSTR]; 73 | char path[XT_BPF_PATH_MAX]; 74 | }; 75 | }; 76 | ``` 77 | 78 | `ebpf_bytes` 字节格式的定义,可以在内核头文件中定义的结构体 [xt_bpf_info_v1](https://git.netfilter.org/iptables/tree/include/linux/netfilter/xt_bpf.h#n27) 中找到。上面的代码例子显示了该结构体的相关部分。 79 | 80 | xt_bpf 模块既支持原始字节码,也支持持久化到某个文件路径的 ebpf 程序。接下来的内容就是我们整合 ebpf 程序和 nftables 的技术详情。 81 | 82 | 有了这些信息,我们就可以通过编写代码创建 netlink 信息,并正确地序列化任何相关的数据字段。这只是第一步,我们还在研究如何将其整合到合适的工具中去,取代发送自定义 netfilter 消息的方式。 83 | 84 | ## 整合 eBPF 85 | 86 | 现在我们需要构建一个 eBPF 程序,并将其加载到一个现有的 nftables 表和链中。刚开始使用 eBPF 可能会有点难度,比如应该使用哪种程序类型、如何编译和加载我们的 eBPF 程序。我们通过做了一些探索和研究,开启了我们的旅程。 87 | 88 | 首先,我们构建了一个示例程序来进行尝试。 89 | 90 | ```C 91 | SEC("socket") 92 | int filter(struct __sk_buff *skb) { 93 | /* get header */ 94 | struct iphdr iph; 95 | if (bpf_skb_load_bytes(skb, 0, &iph, sizeof(iph))) { 96 | return BPF_DROP; 97 | } 98 | 99 | /* read last 5 bytes in payload of udp */ 100 | __u16 pkt_len = bswap_16(iph.tot_len); 101 | char data[5]; 102 | if (bpf_skb_load_bytes(skb, pkt_len - sizeof(data), &data, sizeof(data))) { 103 | return BPF_DROP; 104 | } 105 | 106 | /* only packets with the magic word at the end of the payload are allowed */ 107 | const char SECRET_TOKEN[5] = "xyzzy"; 108 | for (int i = 0; i < sizeof(SECRET_TOKEN); i++) { 109 | if (SECRET_TOKEN[i] != data[i]) { 110 | return BPF_DROP; 111 | } 112 | } 113 | 114 | return BPF_OK; 115 | } 116 | ``` 117 | 118 | 上面摘录的是是一个 eBPF 程序例子,它只接受在网络请求末端有一串“魔法”字符的数据包。我们需要检查数据包的总长度,来找到开始搜索的位置。为了表述更清楚,这个例子省略了错误检查和头文件引用的相关代码。 119 | 120 | 一旦我们编写好了程序,下一步就是把它整合到我们的工具中。我们尝试了一些技术来加载这个程序,比如 BCC、libbpf,甚至我们还自研了一个自定义加载器。最后,我们最终使用了 [cilium的 ebpf 库](https://github.com/cilium/ebpf/),因为我们的控制面程序使用的也是Golang 语言,而 cilium 提供的 ebpf 库使得生成、嵌入和加载 eBPF 程序变得容易很多。 121 | 122 | ```bash 123 | # nft list ruleset 124 | table ip mfw { 125 | chain input { 126 | #match bpf pinned /sys/fs/bpf/mfw/match drop 127 | } 128 | } 129 | ``` 130 | 131 | 一旦程序被编译和持久化,我们就可以使用 `netlink` 命令将匹配的内容添加到 nftables 中。通过列出规则集,就能显示刚刚添加的匹配内容。这真是不可思议,现在我们能在 Magic Firewall 规则集内部署定制的 C 程序,实现了先进的匹配能力了! 132 | 133 | ## 更多魔力 134 | 135 | 随着 eBPF 整合到我们的工具包里,Magic Firewall 服务变成了一种能保护你的网络免受坏人侵害的,更灵活和强大的方式。我们现在能够更深入地研究网络数据包,实现比 nftables 自身提供的更复杂的匹配逻辑。由于我们的防火墙服务,是作为软件运行在所有 Cloudflare 服务器上的,因此可以实现快速迭代和功能更新。 136 | 137 | 这个项目的成果之一是实现 SIP 保护能力,当然,它目前还处于 beta 测试阶段。这仅仅是个开始,目前我们正在探索使用 eBPF 进行协议验证、高级字段匹配、网络请求分析、支持更大的 IP 列表集。 138 | 139 | 欢迎任何人给我们提供帮助! 如果你有其他使用场景和想法,请与我们的客户团队沟通讨论。如果你觉得这项技术很有趣,就来[加入我们的团队吧](https://www.cloudflare.com/careers/)! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 请审校者在合并该 PR 前需要检查以下内容: 2 | 3 | - [ ] Header 设置正确,包括文章的类型、tag、分类、作者、译者、摘要、reviewer 发布时间等信息 4 | - [ ] 关联 Issue 编号 https://github.com/kubesphere-sigs/awesome-cloud-native-blogs/issues/${ISSUE_ID} 5 | - [ ] 该 PR Assigned 给了提交者 6 | - [ ] 指定了 Reviewer 7 | - [ ] 为 PR 明确了 label 信息,如 categories、kind、size、tag、status 8 | - [ ] 所有代码片段都指定了代码语言 9 | - [ ] 文章遵守了路径规范 `YEAR/MONTH/TITLE/index.md`,例如 `2021/08/new-blog/index.md` 10 | - [ ] 图片保存了在 GitHub上,跟文章在同级目录 11 | - [ ] 文章标题简短且语义明确 12 | - [ ] 文章语句通顺,没有语病和格式问题 13 | - [ ] 文中没有不可达的链接 14 | - [ ] 提交者同意授权「KubeSphere 云原生」公众号和 http://kubesphere.com.cn 发布原创或翻译的文章 15 | 16 | 当所有以上问题都确定之后,可以由管理者合并 PR 并关闭关联 issue。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Awesome Cloud Native Blogs 2 | 3 | ## 关于本仓库 4 | 5 | 在本仓库中,您可以: 6 | 7 | - 提交英文文章线索 8 | - 参与翻译 9 | - 提交个人原创文章 10 | - 提交个人已发布文章的转载要求 11 | 12 | 文章题材包括但不限于: 13 | 14 | - Kubernetes 15 | - Service Mesh 16 | - Cloud Native 17 | - Serverless 18 | - DevOps 19 | - Container 20 | 21 | 您提交的文章将首发于: 22 | 23 | - KubeSphere 社区官网:https://kubesphere.com.cn 24 | - 「KubeSphere 云原生」微信公众号 25 | - 其他渠道(保留作者署名或 URL 共享) 26 | 27 | ## 奖励机制 28 | 29 | 为了鼓励和奖励大家对社区的贡献,社区特别设置了奖励机制。凡是通过以上任意方式参与贡献,都可以获得相对应的奖励,包括但不限于:社区贡献者证书(统一发放)、KubeSphere 周边纪念礼品(免费邮寄)。 30 | 如果您还想获取其他类型的奖励,可以提交 Issue,我们将视具体情况判断,要求合理即可满足。 31 | 32 |  33 | 34 | ## 如何参与 35 | 36 | 参与该项目包括两种方式: 37 | 38 | 1. 通过[提交 Issue](https://github.com/kubesphere-sigs/awesome-cloud-native-blogs/issues/new?assignees=&labels=kind%2Ftranslation&template=docs.yaml)的方式提交文章线索 39 | 2. 通过 PR 参与文章翻译或提交原创文章 40 | 3. 原则上所有认领的文章要在 5 个工作日内完成翻译或创作,**可以多人认领同一篇文章协作翻译** 41 | 42 | ### 提交 Issue 43 | 44 | [提交Issue](https://github.com/kubesphere-sigs/awesome-cloud-native-blogs/issues/new?assignees=&labels=kind%2Ftranslation&template=docs.yaml),其中的选项只要打钩即可。 45 | 46 | **注意**:请在 Issue 标题中增加类型前缀,如`Translation:`、`Original:`、`Reprint:`。 47 | 48 | ### 提交 PR 49 | 50 | [提交PR](https://github.com/kubesphere-sigs/awesome-cloud-native-blogs/pulls),可以为译文、原创或个人文章转载,请在文章头部添加元信息,格式如下: 51 | 52 | ```yaml 53 | --- 54 | original: "原文链接或者原创作者的 GitHub 账号" 55 | author: "作者姓名" 56 | translator: "译者的 GitHub 账号" 57 | original: "原文地址" 58 | reviewer: ["审阅者 A 的 GitHub 账号","审阅者 B 的 GitHub 账号"] 59 | title: "标题" 60 | summary: "这里是文章摘要。" 61 | categories: "译文、原创或转载" 62 | tags: ["taga","tagb","tagc"] 63 | originalPublishDate: 2021-08-18 64 | publishDate: 2019-08-18 65 | --- 66 | ``` 67 | 68 | 注:其中 `originalPublishDate` 为所翻译的文章原文的发布日期,`publishDate` 为原创文章或译文的 PR 合并日期。 69 | 70 | ## 关于 PR 的注意事项 71 | 72 | 提交 PR 请注意以下事项: 73 | 74 | - 请在 PR 中标题中增加类型前缀,如`Translation:`、`Original:`、`Reprint:` 75 | - 提交的文件名必须全英文、小写、单词间使用连字符 76 | 77 | 管理员在合并 PR 前请先确认 [PR 模板](