<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>EBPF on 涯余的博客</title><link>https://hangyan.github.io/tags/ebpf/</link><description>Recent content in EBPF on 涯余的博客</description><generator>Hugo</generator><language>zh</language><managingEditor>hang.yan@hotmail.com (涯余)</managingEditor><webMaster>hang.yan@hotmail.com (涯余)</webMaster><lastBuildDate>Sat, 18 Dec 2021 19:29:35 +0800</lastBuildDate><atom:link href="https://hangyan.github.io/tags/ebpf/index.xml" rel="self" type="application/rss+xml"/><item><title>TC 的一些有趣功能</title><link>https://hangyan.github.io/post/2021-tc-ebpf/</link><pubDate>Sat, 18 Dec 2021 19:29:35 +0800</pubDate><author>hang.yan@hotmail.com (涯余)</author><guid>https://hangyan.github.io/post/2021-tc-ebpf/</guid><description>&lt;p&gt;最近在看 TC 和 eBPF 相关的一些介绍。TC 本身的概念比较复杂，想要理清并不容易。加上 eBPF 的引入就更复杂了。本文用来记录与此相关的一些发现。&lt;/p&gt;
&lt;h2 id="clsact"&gt;clsact&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;clsact&lt;/code&gt; 即是用来给 TC + eBPF 是使用的一个 &lt;code&gt;qdisc&lt;/code&gt;. &lt;code&gt;qdisc&lt;/code&gt; 可以简单理解为使用某种算法(FIFO)的 Queue. &lt;code&gt;clsact&lt;/code&gt; 的特点是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不用排队&lt;/li&gt;
&lt;li&gt;主要就是为了 attach ebpf 用的&lt;/li&gt;
&lt;li&gt;在 ingress/egress 都提供了 hook&lt;/li&gt;
&lt;li&gt;lock free&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ingress-与-egress-的不同"&gt;ingress 与 egress 的不同&lt;/h2&gt;
&lt;p&gt;不太确定直接的原因是啥，目前的 qdisc 只在 egress 方向上有。一般的 rate-limiting 都是在 egress 上实现， ingress上的比较困难。一个原因是难以对源头进行限速。&lt;/p&gt;
&lt;h2 id="multiple-qdisc"&gt;multiple qdisc&lt;/h2&gt;
&lt;p&gt;多个 qdisc 在 egress 上是很常见的。加了 clasact 之后就更常见了。那么 clsact 和他们什么关系？&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://hangyan.github.io/images/2021-tc-ebpf/clsact-qdisc.png"&gt;&lt;/p&gt;
&lt;p&gt;这是我从另一个 Topic 摘过来的图。可以看出来， clsact 是在普通的 qdisc 之前的。&lt;/p&gt;
&lt;h2 id="direct-action-mode"&gt;direct-action mode&lt;/h2&gt;
&lt;p&gt;请参考: &lt;a href="https://qmonnet.github.io/whirl-offload/2020/04/11/tc-bpf-direct-action/"&gt;Understanding tc “direct action” mode for BPF&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Cilium 的 Host Firewall 功能</title><link>https://hangyan.github.io/post/2021-cilium-case-1/</link><pubDate>Fri, 10 Dec 2021 22:29:53 +0800</pubDate><author>hang.yan@hotmail.com (涯余)</author><guid>https://hangyan.github.io/post/2021-cilium-case-1/</guid><description>&lt;p&gt;Cilium CNI 之前一直看文档，功能大概了解，eBPF也大略看过。但一直没有深入从代码角度去研究。它的 bpf 相关代码做的比较全，而且比较老，跟现在网上的 BCC, CO-RW 还不太一样，而且代码量又大，所以读起来并不容易。这次借着 Host Firewall 功能的探究，深入看下。不可能面面俱到，但尽量多涉及一些。这篇文章在后面可能会继续更新，如果有新的发现的话。&lt;/p&gt;
&lt;h2 id="一些基本概念"&gt;一些基本概念&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Endpoint&lt;/code&gt;: 可以理解为跟 device 挂钩的概念(host, pod 等等)，还有特殊的 health endpoint等等。Endpoint有ID,也有对应的 Identity 和自己的 Policy. Endpoint 是 Node Scope 的概念。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Identity&lt;/code&gt;: 跟 label 挂钩。同一个deploy的两个Pod, 有不同的endpoint,但有同样的 identity. K8s Node共用同样的 Identity. Cilium 的 NetworkPolicy 即是针对于 Identity的。这个很好理解，二者主要都是使用 label 来做 filter. 跟 Endpoint 不同， Identity 是 Cluster Scope 的概念(label当然跟node无关)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KV Store&lt;/code&gt;: 用来存储一些映射关系。Labels, Identity, Address, Node等这些元素之间的各种映射关系。Cilium 要实现 NetworkPolicy, 那么就需要这些映射关系。比如 Address -&amp;gt; Identity 等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不同的 CNI 在架构方面的差别还是挺大的。比如 KV Store 的需求，有的 CNI 就完全不需要。对 Cilium 来说，当有新的 NetworkPolicy时, agent 来计算 NetworkPolicy,如果发现自己所在的 Node 上有 Endpoint 受影响，那么就需要对此 Endpoint 做一些 Update (Regenerate BPF Programs等等)，其他的 Node 则不需要。不同的 agent 则依赖于 KV Store 来互相交换一些数据。
引用一篇架构图所示:&lt;/p&gt;</description></item><item><title>eBPF Loop</title><link>https://hangyan.github.io/post/2021-ebpf-loop/</link><pubDate>Fri, 03 Dec 2021 15:54:37 +0800</pubDate><author>hang.yan@hotmail.com (涯余)</author><guid>https://hangyan.github.io/post/2021-ebpf-loop/</guid><description>&lt;p&gt;eBPF Loop 是个老大难问题，只要实际开发大概率都会遇到。Kernel 版本越高，loop 的支持也越多。最近在 LWN 上又看到一篇关于 bpf loop 的新实现，如果能合并进去，那么就有三种 loop 得方式了:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;bounded loop: 跟传统编程模式很像，但loop的count必须是已知的&lt;/li&gt;
&lt;li&gt;map iterator: 针对 bpf map 的迭代器，可以用来遍历 map&lt;/li&gt;
&lt;li&gt;bpf_loop 函数: 最新的提议，能让 verifier 处理的更快&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;目前的 verifier 的策略是比较激进的，因为它没法验证所有的场景，所以有可能会对一些实际正确的程序验证不通过。&lt;code&gt;bpf_loop&lt;/code&gt; 主要就是从这个角度来考虑问题，相当于对最开始的 bounded loop 做的一个优化。下面将详细介绍。&lt;/p&gt;
&lt;h2 id="bounded-loop"&gt;Bounded Loop&lt;/h2&gt;
&lt;p&gt;Kernel 5.3 之前, eBPF 不支持 loop. 要想实现类似功能，而且前提是明确 loop 的次数，那么基本上只能靠 &lt;code&gt;unroll&lt;/code&gt; 来实现。示例如下:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#pragma clang loop unroll(full)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cm"&gt;/* Do stuff ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;相当于把 loop 展开。5.3 及之后，就不用这样了。&lt;/p&gt;
&lt;h2 id="map-iterator"&gt;MAP Iterator&lt;/h2&gt;
&lt;p&gt;Bounded Loop 只能解决一部分的问题。总会有 unbounded loop 的场景。5.13 引入了 map iterator, 支持对 bpf map做遍历。考虑到 bpf map 在 eBPF 程序中的普遍性，算是一个比较好的解决方案了.&lt;/p&gt;</description></item><item><title>eBPF Timeline</title><link>https://hangyan.github.io/post/2021-ebpf-timeline/</link><pubDate>Tue, 21 Sep 2021 10:42:52 +0800</pubDate><author>hang.yan@hotmail.com (涯余)</author><guid>https://hangyan.github.io/post/2021-ebpf-timeline/</guid><description>&lt;p&gt;bcc 的文档已经做了很多详细的介绍，关于 kernel 中 eBPF 的变化: &lt;a href="https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md"&gt;https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md&lt;/a&gt;. 本文记录的是我在测试过程中发现的几个比较重要的节点。&lt;/p&gt;
&lt;h2 id="52"&gt;5.2&lt;/h2&gt;
&lt;p&gt;这块没找到详细描述，是根据程序实测出来的。在运行包含 map 的 eBPF 程序时，有如下错误:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;failed to load objects: field Ingress: program _ingress: map .rodata: map create: read- and write-only maps not supported (requires &amp;gt;= v5.2)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;错误信息提示的比较清楚，5.2内核及以上的就可以。不太确定为啥用上了 read/write only的 maps, 猜测是 libbpf 内部的实现机制导致的。因为这个问题，我目前将我测试的 eBPF 程序支撑的最低内核设定为 5.2 了。这点挺可惜的，因为 4.x 的内核对 eBPF 的很多支持已经很完善了， ubuntu 18.04 就是 4.18 的内核。&lt;/p&gt;
&lt;h2 id="58"&gt;5.8&lt;/h2&gt;
&lt;p&gt;kernel 默认带了 BTF 文件。这个 5.8 是不准确的，但确实没查到具体在哪个 kernel 版本默认带的。ubuntu 20.04 没有， 20.10 有了，暂且将其作为开始版本。kernel 对 BTF 的支持从 4.x 就有了，只不过最开始不是默认带的，如果需要，需要自己 打开开关并重新编译内核。如果考虑生产环境，这个其实挺麻烦的。如果设定为只支持自带 BTF 文件的版本，那就省事多了。&lt;/p&gt;</description></item><item><title>eBPF, iptables, nftables</title><link>https://hangyan.github.io/post/2021-ebpf-iptables/</link><pubDate>Mon, 20 Sep 2021 19:56:17 +0800</pubDate><author>hang.yan@hotmail.com (涯余)</author><guid>https://hangyan.github.io/post/2021-ebpf-iptables/</guid><description>&lt;p&gt;把它们三个列在一起，是因为确实有错综复杂的关系。我也想理一理他们在网络方面的前景,比较侧重于 K8s 和 CNI 方面.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;iptables&lt;/code&gt;: kube-proxy最开始基于 iptables 来实现了 service 的负载均衡，只支持随机算法。因为 iptables 本身的局限性，目前提供了 ipvs 模式.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nftables&lt;/code&gt;: 可以说是 iptables 的替代品，更好的API及性能，但是因为 iptables 的遗留使用率太高，暂时难以看到全面替代的可能。&lt;/li&gt;
&lt;li&gt;&lt;code&gt; eBPF&lt;/code&gt;: 影响越来越多，有可能会在很多场景下替代 iptables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是简要的说明。下面将根据不同侧面来详细分析一下。&lt;/p&gt;
&lt;h2 id="iptables"&gt;iptables&lt;/h2&gt;
&lt;p&gt;两个主要问题:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;规则数量线性增长&lt;/li&gt;
&lt;li&gt;更新规则需要整个替换掉，无法保证原子性。更新规则的效率也很低。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="ipset"&gt;ipset&lt;/h2&gt;
&lt;p&gt;ipset 是 iptables 的一个扩展，主要解决的问题是 iptables 的线性增长问题。考虑到 iptables 的现实中的普及性以及大家求稳的心态， ipset 在很多场景下仍是一个不错的选择。其基本思想是用 hash 等数据结构来替代原来的线性匹配，这种设计我们在很多地方都可以见到，比如 ovs, eBPF等等。&lt;/p&gt;
&lt;p&gt;nftables 作为 iptables 的继任者，很多设计已经了 ipset 里的设计。二者有一定的相似性。&lt;/p&gt;
&lt;h2 id="ipvs"&gt;ipvs&lt;/h2&gt;
&lt;p&gt;没找到太好的深入介绍的资料，但根据网上找的测试数据，比 iptables 好很多.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://www.tigera.io/wp-content/uploads/2019/04/Picture1.png"&gt;&lt;/p&gt;
&lt;p&gt;Service 数量少时不明显，数量很大时差距就出来了。&lt;/p&gt;
&lt;h2 id="kube-proxy"&gt;kube-proxy&lt;/h2&gt;
&lt;p&gt;eBPF, iptables, nftables,ipvs, ipset这些东西，最终在 k8s 上的落脚点都是 kube-proxy. kube-proxy实现了各种 Service 的功能，底层用什么，是可能会变的，从最开始的 iptables 到现在的 ipvs, 以后真用其他的东西也说不准. 除了 iptables 和 ipvs, 我们也能找到其他方面的尝试:&lt;/p&gt;</description></item><item><title>eBPF系列2 - XDP</title><link>https://hangyan.github.io/post/2020-11-17-ebpf-2-xdp/</link><pubDate>Tue, 17 Nov 2020 23:16:48 +0800</pubDate><author>hang.yan@hotmail.com (涯余)</author><guid>https://hangyan.github.io/post/2020-11-17-ebpf-2-xdp/</guid><description>&lt;p&gt;XDP是指 &lt;code&gt;eXpress data path&lt;/code&gt;, 基于 ebpf 技术上的 高性能 data path. 其主要的意图是在网络 packet 处理的早期(网卡驱动处, 在数据包到达RX queue之后, 用hook的方式)，让用户可以编写 ebpf 程序来进行一些决策。 这个hook的位置早于所有的内存分配时期(sk_buffer)，也没有上下文切换,系统调用等开销, 所以对于性能的提升是很明显的。测试数据表明，在普通的硬件上， XDP 可以 dop 大概 2600w packets per cpu.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://hangyan.github.io/images/ebpf/xdp-1.png"&gt;&lt;/p&gt;
&lt;p&gt;一个网络包的处理流程大致如下:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;网络包到达网卡&lt;/li&gt;
&lt;li&gt;从 NIC queue 拷贝到内存(DMA-backed ring buffer)中&lt;/li&gt;
&lt;li&gt;网卡驱动使用 NAPI look 触发 soft IRQs&lt;/li&gt;
&lt;li&gt;per cpu 特定线程处理网络包&lt;/li&gt;
&lt;li&gt;分配 socket_buffer(sk_buffer), 作为网络包的基本数据结构&lt;/li&gt;
&lt;li&gt;kernel 填充 metadata, clone sk_buffer 并且交给上层网络处理层&lt;/li&gt;
&lt;li&gt;IP layer 做校验, netfilter hook 处理&lt;/li&gt;
&lt;li&gt;如果 netfilter 未 drop 此包,交给更上层网络层处理&lt;/li&gt;
&lt;li&gt;&amp;hellip;.&lt;/li&gt;
&lt;li&gt;最终数据被 copy 到 userspace (recv,read,poll等网络调用获取)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中, XDP hook 在数据到达 NIC RX queue 之后即触发(上面第二步之后).而 iptables 等的处理(上面第八步之后)非常靠后, 需要分配大量资源来处理网络包.&lt;/p&gt;</description></item><item><title>eBPF系列1 - 介绍</title><link>https://hangyan.github.io/post/ebpf-1/</link><pubDate>Tue, 22 Sep 2020 22:39:41 +0800</pubDate><author>hang.yan@hotmail.com (涯余)</author><guid>https://hangyan.github.io/post/ebpf-1/</guid><description>&lt;p&gt;一直想写(整理)一些关于 eBPF 的文章。在我眼中，它是 &lt;code&gt;未来技术&lt;/code&gt; 之一。 最近零零碎碎看了一些，感觉信息太多，怕是永远也看不完，索性开始一遍看一边记笔记吧。这一篇是介绍。&lt;/p&gt;
&lt;h2 id="ebpf-是什么"&gt;eBPF 是什么&lt;/h2&gt;
&lt;p&gt;先介绍下 eBPF 的前身: BPF -&amp;gt; &lt;code&gt;Berkeley Packet Filter&lt;/code&gt;. 看名字就知道，主要是用于网络 packet 的过滤用的。 过滤程序是运行于基于寄存器的虚拟机之上．　BPF 展示了在内核中运行用户程序的良好开端．但它的缺点也很明显，虚拟机的设计以及指令集都比较落后，跟不上现代处理器的发展(尤其是多核方面)，也无法利用现在的 64bit 寄存器．&lt;/p&gt;
&lt;p&gt;eBPF extend 了 BPF, 将其机制大大地泛化，不止用于网络数据包的处理，而是可以 hook 在任意的系统调用上, 这样它就变成了一个几乎具有无限功能的系统。 在硬件上，它跟现代的处理器的指令集更为贴近,并且能充分利用现在数量众多的 64bit 寄存器．这样就使 JIT 编译器的产生变为了可能，大大提升了性能.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://ebpf.io/static/syscall_hook-b4f7d64d4d04806a1de60126926d5f3a.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://ebpf.io/static/hook_overview-99c69bbff092c35b9c83f00a80fed240.png"&gt;&lt;/p&gt;
&lt;p&gt;在没有　eBPF 只之前，如果我们想对内核行为进行修改，基本上就是往内核里添加代码或者通过 kernel modules 进行．这二者的门槛都不低，所以影响有限．eBPF 提供了一种新的可能性，通过普通的编程方式，去控制内核的行为，同时保证了高性能．这就是它之所以独特和具有极大潜力的主要原因．&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://img2020.cnblogs.com/blog/1334952/202008/1334952-20200806131434176-1093013946.png"&gt;&lt;/p&gt;
&lt;h2 id="程序校验"&gt;程序校验&lt;/h2&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://ebpf.io/static/loader-7eec5ccd8f6fbaf055256da4910acd5a.png"&gt;&lt;/p&gt;
&lt;p&gt;当 eBPF 程序被加载进内核之后，要先做一些程序校验工作, 比如:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;权限是否具备。 加载 eBPF 的程序是否有权限加载.一般来讲，程序必须得是 root 用户运行的或者有 &lt;code&gt;CAP_BPF&lt;/code&gt; 权限才能加载。 如果开启了 &lt;code&gt;unprivileged eBPF&lt;/code&gt;, 那么普通程序也可以加载一些功能受限的 eBPF 程序(比如不允许指针操作)。&lt;/li&gt;
&lt;li&gt;eBPF 程序是否有可能 crash&lt;/li&gt;
&lt;li&gt;在每次指令执行前后都需要校验虚拟机的状态，保证寄存器和栈的状态都是有效的. 校验器不会检查程序的每条路径，它能够知道程序的当前状态是否是已经检查过的程序的子集。由于前面的所有路径都必须是有效的(否则程序会加载失败)，当前的路径也必须是有效的，因此允许验证器“修剪”当前分支并跳过其模拟阶段。&lt;/li&gt;
&lt;li&gt;不能访问未初始化的变量或者越界访问&lt;/li&gt;
&lt;li&gt;程序大小有 limit.&lt;/li&gt;
&lt;li&gt;复杂程度有 limit. 检查程序会估算所有的执行路径以评估其复杂程度。&lt;/li&gt;
&lt;li&gt;eBPF 是否会无法终止运行(比如 loop forever)&lt;/li&gt;
&lt;li&gt;不同类型的 eBPF 程序只能访问特定类型的系统调用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;之所以有这么多检查，就是因为它能做的事情太多了，也太有可能造成破坏了，所以在校验阶段就尽量多做一些。&lt;/p&gt;</description></item></channel></rss>