mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 18:24:27 +08:00
Deploying to gh-pages from @ eunomia-bpf/bpf-developer-tutorial@bbd7b1638d 🚀
This commit is contained in:
@@ -217,12 +217,11 @@
|
||||
<h3 id="了解如何开发-ebpf-程序10-15h"><a class="header" href="#了解如何开发-ebpf-程序10-15h">了解如何开发 eBPF 程序(10-15h)</a></h3>
|
||||
<p>了解并尝试一下 eBPF 开发框架:</p>
|
||||
<ul>
|
||||
<li>bpftrace 教程,对于最简单的应用来说,bpftrace 可能是最方便的:https://eunomia.dev/zh/tutorials/bpftrace-tutorial/ (试试,1h)</li>
|
||||
<li>bpftrace 教程,对于最简单的应用来说,bpftrace 可能是最方便的:<a href="https://eunomia.dev/zh/tutorials/bpftrace-tutorial/">https://eunomia.dev/zh/tutorials/bpftrace-tutorial/</a> (试试,1h)</li>
|
||||
<li>BCC 开发各类小工具的例子:<a href="https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md">https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md</a> (跑一遍,3-4h)</li>
|
||||
<li>libbpf 的一些例子:<a href="https://github.com/libbpf/libbpf-bootstrap">https://github.com/libbpf/libbpf-bootstrap</a> (选感兴趣的运行一下,并阅读一下源代码,2h)</li>
|
||||
<li>基于 libbpf 和 eunomia-bpf 的教程:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> (阅读 1-10 的部分,3-4h)</li>
|
||||
<li>基于 C 语言 libbpf, Go 语言或者 Rust 语言和 eunomia-bpf 的教程:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> (阅读 1-20 的部分,3-8h)</li>
|
||||
</ul>
|
||||
<p>其他开发框架:Go 语言或者 Rust 语言,请自行搜索并且尝试(0-2h)</p>
|
||||
<p>有任何问题或者想了解的东西,不管是不是和本项目相关,都可以在本项目的 discussions 里面开始讨论。</p>
|
||||
<p>回答一些问题,并且进行一些尝试(2-5h):</p>
|
||||
<ol>
|
||||
@@ -283,15 +282,13 @@ eBPF 程序每次执行时候都需要进行编译,编译则需要用户配置
|
||||
字节码装载到内核中。同样的,<code>libbpf-bootstrap</code> 也有非常完备的入门教程,用户可以在<a href="https://nakryiko.com/posts/libbpf-bootstrap/">该处</a>
|
||||
得到详细的入门操作介绍。</p>
|
||||
<h3 id="eunomia-bpf"><a class="header" href="#eunomia-bpf">eunomia-bpf</a></h3>
|
||||
<p>开发、构建和分发 eBPF 一直以来都是一个高门槛的工作,使用 BCC、bpftrace 等工具开发效率高、可移植性好,但是分发部署时需要安装 LLVM、Clang 等编译环境,每次运行的时候执行本地或远程编译过程,资源消耗较大;使用原生的 CO-RE libbpf 时又需要编写不少用户态加载代码来帮助 eBPF 程序正确加载和从内核中获取上报的信息,同时对于 eBPF 程序的分发、管理也没有很好地解决方案。</p>
|
||||
<p><a href="https://github.com/eunomia-bpf/eunomia-bpf">eunomia-bpf</a> 是一个开源的 eBPF 动态加载运行时和开发工具链,是为了简化 eBPF 程序的开发、构建、分发、运行而设计的,基于 libbpf 的 CO-RE 轻量级开发框架。</p>
|
||||
<p>使用 eunomia-bpf ,可以:</p>
|
||||
<ul>
|
||||
<li>在编写 eBPF 程序或工具时只编写内核态代码,自动获取内核态导出信息,并作为模块动态加载;</li>
|
||||
<li>使用 WASM 进行用户态交互程序的开发,在 WASM 虚拟机内部控制整个 eBPF 程序的加载和执行,以及处理相关数据;</li>
|
||||
<li>eunomia-bpf 可以将预编译的 eBPF 程序打包为通用的 JSON 或 WASM 模块,跨架构和内核版本进行分发,无需重新编译即可动态加载运行。</li>
|
||||
</ul>
|
||||
<p>eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的 BCC、原生 libbpf 等框架,大幅简化了 eBPF 程序的开发流程,在大多数时候只需编写内核态代码,即可轻松构建、打包、发布完整的 eBPF 应用,同时内核态 eBPF 代码保证和主流的 libbpf,libbpfgo,libbpf-rs 等开发框架的 100% 兼容性。需要编写用户态代码的时候,也可以借助 Webassembly 实现通过多种语言进行用户态开发。和 bpftrace 等脚本工具相比, eunomia-bpf 保留了类似的便捷性, 同时不仅局限于 trace 方面, 可以用于更多的场景, 如网络、安全等等。</p>
|
||||
<p>eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的 BCC、原生 libbpf 等框架,简化了 eBPF 程序的开发流程,在大多数时候只需编写内核态代码,即可轻松构建、打包、发布完整的 eBPF 应用。</p>
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>eunomia-bpf 项目 Github 地址: <a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a></li>
|
||||
|
||||
@@ -267,7 +267,7 @@ Packing ebpf object and config into package.json...
|
||||
</code></pre>
|
||||
<p>然后使用 ecli 运行编译后的程序:</p>
|
||||
<pre><code class="language-console">$ sudo ./ecli run package.json
|
||||
Runing eBPF program...
|
||||
Running eBPF program...
|
||||
</code></pre>
|
||||
<p>运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:</p>
|
||||
<pre><code class="language-console">$ sudo cat /sys/kernel/debug/tracing/trace_pipe | grep "BPF triggered sys_enter_write"
|
||||
|
||||
@@ -173,19 +173,81 @@
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="ebpf-入门实践教程二十一使用-xdp-实现可编程包处理"><a class="header" href="#ebpf-入门实践教程二十一使用-xdp-实现可编程包处理">eBPF 入门实践教程二十一:使用 xdp 实现可编程包处理</a></h1>
|
||||
<h2 id="背景"><a class="header" href="#背景">背景</a></h2>
|
||||
<p>xdp(eXpress Data Path)是 Linux 内核中新兴的一种绕过内核的、可编程的包处理方案。相较于 cBPF,xdp 的挂载点非常底层,位于网络设备驱动的软中断处理过程,甚至早于 skb_buff 结构的分配。因此,在 xdp 上挂载 eBPF 程序适用于很多简单但次数极多的包处理操作(如防御 Dos 攻击),可以达到很高的性能(24Mpps/core)。</p>
|
||||
<h2 id="xdp-概述"><a class="header" href="#xdp-概述">XDP 概述</a></h2>
|
||||
<p>xdp 不是第一个支持可编程包处理的系统,在此之前,以 DPDK(Data Plane Development Kit)为代表的内核旁路方案甚至能够取得更高的性能,其思路为完全绕过内核,由用户态的网络应用接管网络设备,从而避免了用户态和内核态的切换开销。然而,这样的方式具有很多天然的缺陷:</p>
|
||||
<h1 id="ebpf-入门实践教程二十一-使用-xdp-进行可编程数据包处理"><a class="header" href="#ebpf-入门实践教程二十一-使用-xdp-进行可编程数据包处理">eBPF 入门实践教程二十一: 使用 XDP 进行可编程数据包处理</a></h1>
|
||||
<p>在本教程中,我们将介绍 XDP(eXpress Data Path),并通过一个简单的例子帮助你入门。之后,我们将探讨更高级的 XDP 应用,例如负载均衡器、防火墙及其他实际应用。如果你对 eBPF 或 XDP 感兴趣,请在 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">Github</a> 上为我们点赞!</p>
|
||||
<h2 id="什么是-xdp"><a class="header" href="#什么是-xdp">什么是 XDP?</a></h2>
|
||||
<p>XDP 是 Linux 内核中的一种高性能可编程数据路径,专为网络接口级的数据包处理而设计。通过将 eBPF 程序直接附加到网络设备驱动程序上,XDP 能够在数据包到达内核网络栈之前拦截并处理它们。这使得 XDP 能够进行极低延迟和高效的数据包处理,非常适合如 DDoS 防护、负载均衡和流量过滤等任务。实际上,XDP 每核心的吞吐量可以高达 <strong>每秒 2400 万包(Mpps)</strong>。</p>
|
||||
<h3 id="为什么选择-xdp"><a class="header" href="#为什么选择-xdp">为什么选择 XDP?</a></h3>
|
||||
<p>XDP 运行在比传统 Linux 网络组件(如 cBPF)更低的层级,在网络设备驱动程序的软中断上下文中执行。它能够在数据包被内核标准网络栈处理之前对其进行处理,避免了创建 Linux 中表示网络数据包的 <code>skb_buff</code> 结构。这种早期处理为简单但频繁的操作(如丢弃恶意数据包或负载均衡服务器)带来了显著的性能提升。</p>
|
||||
<p>与其他数据包处理机制相比,XDP 在性能和可用性之间取得了平衡,它利用了 Linux 内核的安全性和可靠性,同时通过可编程的 eBPF 提供了灵活性。</p>
|
||||
<h2 id="xdp-与其他方法的比较"><a class="header" href="#xdp-与其他方法的比较">XDP 与其他方法的比较</a></h2>
|
||||
<p>在 XDP 出现之前,一些解决方案通过完全绕过内核来加速数据包处理。其中一个显著的例子是 <strong>DPDK</strong>(数据平面开发工具包)。DPDK 允许用户空间应用程序直接控制网络设备,从而实现非常高的性能。然而,这种方法也存在一些权衡:</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p><strong>缺乏内核集成</strong>:DPDK 及其他内核绕过解决方案无法利用现有的内核网络功能,开发者必须在用户空间重新实现许多协议和功能。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>安全边界</strong>:这些绕过技术破坏了内核的安全模型,使得难以利用内核提供的安全工具。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>用户空间与内核的转换开销</strong>:当用户空间数据包处理需要与传统内核网络交互时(例如基于套接字的应用程序),数据包必须重新注入到内核中,增加了开销和复杂性。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>专用 CPU 使用</strong>:为了处理高流量,DPDK 和类似解决方案通常需要专用的 CPU 核心来处理数据包,这限制了通用系统的可扩展性和效率。</p>
|
||||
</li>
|
||||
</ol>
|
||||
<p>另一个替代 XDP 的方法是使用 Linux 网络栈中的 <strong>内核模块</strong> 或 <strong>挂钩</strong>。虽然这种方法可以很好地集成现有的内核功能,但它需要大量的内核修改,且由于在数据包处理管道的后期运行,无法提供与 XDP 相同的性能优势。</p>
|
||||
<h3 id="xdp--ebpf-的优势"><a class="header" href="#xdp--ebpf-的优势">XDP + eBPF 的优势</a></h3>
|
||||
<p>XDP 与 eBPF 结合提供了介于内核绕过方案(如 DPDK)和内核集成方案之间的中间地带。以下是 XDP + eBPF 脱颖而出的原因:</p>
|
||||
<ul>
|
||||
<li>无法与内核中成熟的网络模块集成,而不得不在用户态将其重新实现;</li>
|
||||
<li>破坏了内核的安全边界,使得内核提供的很多网络工具变得不可用;</li>
|
||||
<li>在与常规的 socket 交互时,需要从用户态重新将包注入到内核;</li>
|
||||
<li>需要占用一个或多个单独的 CPU 来进行包处理;</li>
|
||||
<li>
|
||||
<p><strong>高性能</strong>:通过在网络接口卡(NIC)驱动程序级别拦截数据包,XDP 可以实现接近线速的性能,用于丢弃、重定向或负载均衡数据包,同时保持低资源消耗。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>内核集成</strong>:与 DPDK 不同,XDP 在 Linux 内核中工作,允许与现有的内核网络栈和工具(如 <code>iptables</code>、<code>nftables</code> 或套接字)无缝交互。无需在用户空间重新实现网络协议。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>安全性</strong>:eBPF 虚拟机确保用户定义的 XDP 程序是被隔离的,不会对内核造成不稳定影响。eBPF 的安全模型防止恶意或有缺陷的代码损害系统,提供了一个安全的可编程数据包处理环境。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>不需要专用 CPU</strong>:XDP 允许数据包处理而无需将整个 CPU 核心专用于网络任务。这提高了系统的整体效率,允许更灵活的资源分配。</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>总的来说,XDP + eBPF 提供了一种强大的可编程数据包处理解决方案,结合了高性能与内核集成的灵活性和安全性。它消除了完全绕过内核方案的缺点,同时保留了内核安全性和功能的优势。</p>
|
||||
<h2 id="xdp-的项目和应用案例"><a class="header" href="#xdp-的项目和应用案例">XDP 的项目和应用案例</a></h2>
|
||||
<p>XDP 已经在许多高调的项目中得到应用,这些项目展示了它在实际网络场景中的强大功能和灵活性:</p>
|
||||
<h3 id="1-cilium"><a class="header" href="#1-cilium">1. <strong>Cilium</strong></a></h3>
|
||||
<ul>
|
||||
<li><strong>描述</strong>:Cilium 是一个为云原生环境(尤其是 Kubernetes)设计的开源网络、安全和可观测性工具。它利用 XDP 实现高性能的数据包过滤和负载均衡。</li>
|
||||
<li><strong>应用案例</strong>:Cilium 将数据包过滤和安全策略卸载到 XDP,实现高吞吐量和低延迟的容器化环境流量管理,同时不牺牲可扩展性。</li>
|
||||
<li><strong>链接</strong>:<a href="https://cilium.io/">Cilium</a></li>
|
||||
</ul>
|
||||
<h3 id="2-katran"><a class="header" href="#2-katran">2. <strong>Katran</strong></a></h3>
|
||||
<ul>
|
||||
<li><strong>描述</strong>:Katran 是由 Facebook 开发的第 4 层负载均衡器,优化了高可扩展性和性能。它使用 XDP 处理数据包转发,开销极小。</li>
|
||||
<li><strong>应用案例</strong>:Katran 每秒处理数百万个数据包,高效地将流量分配到后端服务器上,利用 XDP 在大规模数据中心中实现低延迟和高性能的负载均衡。</li>
|
||||
<li><strong>链接</strong>:<a href="https://github.com/facebookincubator/katran">Katran GitHub</a></li>
|
||||
</ul>
|
||||
<h3 id="3-cloudflare-的-xdp-ddos-保护"><a class="header" href="#3-cloudflare-的-xdp-ddos-保护">3. <strong>Cloudflare 的 XDP DDoS 保护</strong></a></h3>
|
||||
<ul>
|
||||
<li><strong>描述</strong>:Cloudflare 已经实现了基于 XDP 的实时 DDoS 缓解。通过在 NIC 级别处理数据包,Cloudflare 能够在恶意流量进入网络栈之前过滤掉攻击流量,最小化 DDoS 攻击对其系统的影响。</li>
|
||||
<li><strong>应用案例</strong>:Cloudflare 利用 XDP 在管道早期丢弃恶意数据包,保护其基础设施免受大规模 DDoS 攻击,同时保持对合法流量的高可用性。</li>
|
||||
<li><strong>链接</strong>:<a href="https://blog.cloudflare.com/l4drop-xdp-ebpf-based-ddos-mitigations/">Cloudflare 博客关于 XDP</a></li>
|
||||
</ul>
|
||||
<p>这些项目展示了 XDP 在不同领域的可扩展和高效的数据包处理能力,从安全和负载均衡到云原生网络。</p>
|
||||
<h3 id="为什么选择-xdp-而不是其他方法"><a class="header" href="#为什么选择-xdp-而不是其他方法">为什么选择 XDP 而不是其他方法?</a></h3>
|
||||
<p>与传统方法(如 <code>iptables</code>、<code>nftables</code> 或 <code>tc</code>)相比,XDP 提供了几个明显的优势:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>速度与低开销</strong>:XDP 直接在 NIC 驱动程序中运行,绕过了内核的大部分开销,使数据包处理更快。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>可定制性</strong>:XDP 允许开发人员通过 eBPF 创建自定义的数据包处理程序,提供比传统工具(如 <code>iptables</code>)更大的灵活性和细粒度控制。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>资源效率</strong>:XDP 不需要像 DPDK 等用户空间解决方案那样将整个 CPU 核心专用于数据包处理,因此它是高性能网络的更高效选择。</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>除此之外,利用内核模块和内核网络协议栈中的 hook 点也是一种思路,然而前者对内核的改动大,出错的代价高昂;后者在整套包处理流程中位置偏后,其效率不够理想。</p>
|
||||
<p>总而言之,xdp + eBPF 为可编程包处理系统提出了一种更为稳健的思路,在某种程度上权衡了上述方案的种种优点和不足,获取较高性能的同时又不会对内核的包处理流程进行过多的改变,同时借助 eBPF 虚拟机的优势将用户定义的包处理过程进行隔离和限制,提高了安全性。</p>
|
||||
<h2 id="编写-ebpf-程序"><a class="header" href="#编写-ebpf-程序">编写 eBPF 程序</a></h2>
|
||||
<pre><code class="language-C">#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="在-andorid-上使用-ebpf-程序"><a class="header" href="#在-andorid-上使用-ebpf-程序">在 Andorid 上使用 eBPF 程序</a></h1>
|
||||
<h1 id="在-android-上使用-ebpf-程序"><a class="header" href="#在-android-上使用-ebpf-程序">在 Android 上使用 eBPF 程序</a></h1>
|
||||
<blockquote>
|
||||
<p>本文主要记录了笔者在 Android Studio Emulator 中测试高版本 Android Kernel 对基于 libbpf 的 CO-RE 技术支持程度的探索过程、结果和遇到的问题。
|
||||
测试采用的方式是在 Android Shell 环境下构建 Debian 环境,并基于此尝试构建 eunomia-bpf 工具链、运行其测试用例。</p>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// xdp_lb.bpf.c
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/in.h>
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(max_entries, 2); // Two backends
|
||||
__type(key, __u32);
|
||||
__type(value, __u32); // Backend IPs
|
||||
} backends SEC(".maps");
|
||||
|
||||
SEC("xdp")
|
||||
int xdp_load_balancer(struct xdp_md *ctx) {
|
||||
void *data_end = (void *)(long)ctx->data_end;
|
||||
void *data = (void *)(long)ctx->data;
|
||||
|
||||
struct ethhdr *eth = data;
|
||||
if ((void *)(eth + 1) > data_end)
|
||||
return XDP_PASS;
|
||||
|
||||
if (eth->h_proto != __constant_htons(ETH_P_IP))
|
||||
return XDP_PASS;
|
||||
|
||||
struct iphdr *iph = (struct iphdr *)(eth + 1);
|
||||
if ((void *)(iph + 1) > data_end)
|
||||
return XDP_PASS;
|
||||
|
||||
if (iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP)
|
||||
return XDP_PASS;
|
||||
|
||||
__u32 key = 0;
|
||||
static __u32 cnt = 0;
|
||||
__u32 *backend_ip = bpf_map_lookup_elem(&backends, &key);
|
||||
|
||||
if (!backend_ip)
|
||||
return XDP_PASS;
|
||||
|
||||
cnt = (cnt + 1) % 2; // Round-robin
|
||||
key = cnt;
|
||||
backend_ip = bpf_map_lookup_elem(&backends, &key);
|
||||
|
||||
if (backend_ip) {
|
||||
iph->daddr = *backend_ip; // Redirect to the backend IP
|
||||
iph->check = 0; // Needs recomputation in real cases
|
||||
}
|
||||
|
||||
return XDP_TX; // Transmit modified packet back
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -1,83 +0,0 @@
|
||||
// xdp_lb.c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <bpf/bpf.h>
|
||||
#include <net/if.h>
|
||||
#include <linux/if_link.h>
|
||||
#include "xdp_lb.skel.h" // This header is auto-generated by bpftool
|
||||
|
||||
#define IFACE "eth0" // Replace with your network interface
|
||||
|
||||
static int set_up_backends(struct xdp_lb_bpf *skel) {
|
||||
__u32 backend1 = htonl(0xC0A80102); // 192.168.1.2
|
||||
__u32 backend2 = htonl(0xC0A80103); // 192.168.1.3
|
||||
__u32 key = 0;
|
||||
|
||||
if (bpf_map_update_elem(bpf_map__fd(skel->maps.backends), &key, &backend1, BPF_ANY) < 0) {
|
||||
fprintf(stderr, "Failed to update backend 1\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
key = 1;
|
||||
if (bpf_map_update_elem(bpf_map__fd(skel->maps.backends), &key, &backend2, BPF_ANY) < 0) {
|
||||
fprintf(stderr, "Failed to update backend 2\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
struct xdp_lb_bpf *skel;
|
||||
int err, ifindex;
|
||||
|
||||
// Load and verify the eBPF skeleton
|
||||
skel = xdp_lb_bpf__open();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "Failed to open and load skeleton\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Load eBPF program
|
||||
err = xdp_lb_bpf__load(skel);
|
||||
if (err) {
|
||||
fprintf(stderr, "Failed to load BPF program: %d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set up the backend IP addresses
|
||||
if (set_up_backends(skel) < 0) {
|
||||
fprintf(stderr, "Failed to set up backend IP addresses\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get interface index
|
||||
ifindex = if_nametoindex(IFACE);
|
||||
if (ifindex == 0) {
|
||||
perror("if_nametoindex");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Attach the XDP program
|
||||
err = bpf_xdp_attach(ifindex, bpf_program__fd(skel->progs.xdp_load_balancer), XDP_FLAGS_SKB_MODE, NULL);
|
||||
if (err) {
|
||||
fprintf(stderr, "Failed to attach XDP program: %d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("XDP Load Balancer is running on interface %s...\n", IFACE);
|
||||
sleep(60); // Keep running for 60 seconds
|
||||
|
||||
// Detach the XDP program before exiting
|
||||
err = bpf_xdp_detach(ifindex, XDP_FLAGS_SKB_MODE, NULL);
|
||||
if (err) {
|
||||
fprintf(stderr, "Failed to detach XDP program: %d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
xdp_lb_bpf__destroy(skel);
|
||||
|
||||
return 0;
|
||||
}
|
||||
6
41-xdp-tcpdump/.gitignore
vendored
Normal file
6
41-xdp-tcpdump/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.output
|
||||
uprobe
|
||||
merge-btf
|
||||
*.btf
|
||||
xdp_lb
|
||||
xdp-tcpdump
|
||||
@@ -24,7 +24,7 @@ INCLUDES := -I$(OUTPUT) -I../third_party/libbpf/include/uapi -I$(dir $(VMLINUX))
|
||||
CFLAGS := -g -Wall
|
||||
ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS)
|
||||
|
||||
APPS = funclatency
|
||||
APPS = xdp-tcpdump
|
||||
|
||||
CARGO ?= $(shell which cargo)
|
||||
ifeq ($(strip $(CARGO)),)
|
||||
107
41-xdp-tcpdump/xdp-tcpdump.bpf.c
Normal file
107
41-xdp-tcpdump/xdp-tcpdump.bpf.c
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
|
||||
#define ETH_P_IP 0x0800
|
||||
|
||||
// Define the ring buffer map
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 1 << 24); // 16 MB buffer
|
||||
} rb SEC(".maps");
|
||||
|
||||
// Helper function to check if the packet is TCP
|
||||
static bool is_tcp(struct ethhdr *eth, void *data_end)
|
||||
{
|
||||
// Ensure Ethernet header is within bounds
|
||||
if ((void *)(eth + 1) > data_end)
|
||||
return false;
|
||||
|
||||
// Only handle IPv4 packets
|
||||
if (bpf_ntohs(eth->h_proto) != ETH_P_IP)
|
||||
return false;
|
||||
|
||||
struct iphdr *ip = (struct iphdr *)(eth + 1);
|
||||
|
||||
// Ensure IP header is within bounds
|
||||
if ((void *)(ip + 1) > data_end)
|
||||
return false;
|
||||
|
||||
// Check if the protocol is TCP
|
||||
if (ip->protocol != IPPROTO_TCP)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
int xdp_pass(struct xdp_md *ctx)
|
||||
{
|
||||
// Pointers to packet data
|
||||
void *data = (void *)(long)ctx->data;
|
||||
void *data_end = (void *)(long)ctx->data_end;
|
||||
|
||||
// Parse Ethernet header
|
||||
struct ethhdr *eth = data;
|
||||
|
||||
// Check if the packet is a TCP packet
|
||||
if (!is_tcp(eth, data_end)) {
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
// Cast to IP header
|
||||
struct iphdr *ip = (struct iphdr *)(eth + 1);
|
||||
|
||||
// Calculate IP header length
|
||||
int ip_hdr_len = ip->ihl * 4;
|
||||
if (ip_hdr_len < sizeof(struct iphdr)) {
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
// Ensure IP header is within packet bounds
|
||||
if ((void *)ip + ip_hdr_len > data_end) {
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
// Parse TCP header
|
||||
struct tcphdr *tcp = (struct tcphdr *)((unsigned char *)ip + ip_hdr_len);
|
||||
|
||||
// Ensure TCP header is within packet bounds
|
||||
if ((void *)(tcp + 1) > data_end) {
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
// Define the number of bytes you want to capture from the TCP header
|
||||
// Typically, the TCP header is 20 bytes, but with options, it can be longer
|
||||
// Here, we'll capture the first 32 bytes to include possible options
|
||||
const int tcp_header_bytes = 32;
|
||||
|
||||
// Ensure that the desired number of bytes does not exceed packet bounds
|
||||
if ((void *)tcp + tcp_header_bytes > data_end) {
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
// Reserve space in the ring buffer
|
||||
void *ringbuf_space = bpf_ringbuf_reserve(&rb, tcp_header_bytes, 0);
|
||||
if (!ringbuf_space) {
|
||||
return XDP_PASS; // If reservation fails, skip processing
|
||||
}
|
||||
|
||||
// Copy the TCP header bytes into the ring buffer
|
||||
// Using a loop to ensure compliance with eBPF verifier
|
||||
for (int i = 0; i < tcp_header_bytes; i++) {
|
||||
// Accessing each byte safely within bounds
|
||||
unsigned char byte = *((unsigned char *)tcp + i);
|
||||
((unsigned char *)ringbuf_space)[i] = byte;
|
||||
}
|
||||
|
||||
// Submit the data to the ring buffer
|
||||
bpf_ringbuf_submit(ringbuf_space, 0);
|
||||
|
||||
// Optional: Print a debug message (will appear in kernel logs)
|
||||
bpf_printk("Captured TCP header (%d bytes)", tcp_header_bytes);
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
char __license[] SEC("license") = "GPL";
|
||||
165
41-xdp-tcpdump/xdp-tcpdump.c
Normal file
165
41-xdp-tcpdump/xdp-tcpdump.c
Normal file
@@ -0,0 +1,165 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <bpf/libbpf.h>
|
||||
#include <bpf/bpf.h>
|
||||
|
||||
#include "xdp-tcpdump.skel.h" // Generated skeleton header
|
||||
|
||||
// Callback function to handle events from the ring buffer
|
||||
static int handle_event(void *ctx, void *data, size_t data_sz)
|
||||
{
|
||||
if (data_sz < 20) { // Minimum TCP header size
|
||||
fprintf(stderr, "Received incomplete TCP header\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse the raw TCP header bytes
|
||||
struct tcphdr {
|
||||
uint16_t source;
|
||||
uint16_t dest;
|
||||
uint32_t seq;
|
||||
uint32_t ack_seq;
|
||||
uint16_t res1:4,
|
||||
doff:4,
|
||||
fin:1,
|
||||
syn:1,
|
||||
rst:1,
|
||||
psh:1,
|
||||
ack:1,
|
||||
urg:1,
|
||||
ece:1,
|
||||
cwr:1;
|
||||
uint16_t window;
|
||||
uint16_t check;
|
||||
uint16_t urg_ptr;
|
||||
// Options and padding may follow
|
||||
} __attribute__((packed));
|
||||
|
||||
if (data_sz < sizeof(struct tcphdr)) {
|
||||
fprintf(stderr, "Data size (%zu) less than TCP header size\n", data_sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tcphdr *tcp = (struct tcphdr *)data;
|
||||
|
||||
// Convert fields from network byte order to host byte order
|
||||
uint16_t source_port = ntohs(tcp->source);
|
||||
uint16_t dest_port = ntohs(tcp->dest);
|
||||
uint32_t seq = ntohl(tcp->seq);
|
||||
uint32_t ack_seq = ntohl(tcp->ack_seq);
|
||||
uint16_t window = ntohs(tcp->window);
|
||||
|
||||
// Extract flags
|
||||
uint8_t flags = 0;
|
||||
flags |= (tcp->fin) ? 0x01 : 0x00;
|
||||
flags |= (tcp->syn) ? 0x02 : 0x00;
|
||||
flags |= (tcp->rst) ? 0x04 : 0x00;
|
||||
flags |= (tcp->psh) ? 0x08 : 0x00;
|
||||
flags |= (tcp->ack) ? 0x10 : 0x00;
|
||||
flags |= (tcp->urg) ? 0x20 : 0x00;
|
||||
flags |= (tcp->ece) ? 0x40 : 0x00;
|
||||
flags |= (tcp->cwr) ? 0x80 : 0x00;
|
||||
|
||||
printf("Captured TCP Header:\n");
|
||||
printf(" Source Port: %u\n", source_port);
|
||||
printf(" Destination Port: %u\n", dest_port);
|
||||
printf(" Sequence Number: %u\n", seq);
|
||||
printf(" Acknowledgment Number: %u\n", ack_seq);
|
||||
printf(" Data Offset: %u\n", tcp->doff);
|
||||
printf(" Flags: 0x%02x\n", flags);
|
||||
printf(" Window Size: %u\n", window);
|
||||
printf("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct xdp_tcpdump_bpf *skel;
|
||||
struct ring_buffer *rb = NULL;
|
||||
int ifindex;
|
||||
int err;
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <ifname>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *ifname = argv[1];
|
||||
ifindex = if_nametoindex(ifname);
|
||||
if (ifindex == 0)
|
||||
{
|
||||
fprintf(stderr, "Invalid interface name %s\n", ifname);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Open and load BPF application */
|
||||
skel = xdp_tcpdump_bpf__open();
|
||||
if (!skel)
|
||||
{
|
||||
fprintf(stderr, "Failed to open BPF skeleton\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Load & verify BPF programs */
|
||||
err = xdp_tcpdump_bpf__load(skel);
|
||||
if (err)
|
||||
{
|
||||
fprintf(stderr, "Failed to load and verify BPF skeleton: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Attach XDP program */
|
||||
err = xdp_tcpdump_bpf__attach(skel);
|
||||
if (err)
|
||||
{
|
||||
fprintf(stderr, "Failed to attach BPF skeleton: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Attach the XDP program to the specified interface */
|
||||
skel->links.xdp_pass = bpf_program__attach_xdp(skel->progs.xdp_pass, ifindex);
|
||||
if (!skel->links.xdp_pass)
|
||||
{
|
||||
err = -errno;
|
||||
fprintf(stderr, "Failed to attach XDP program: %s\n", strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("Successfully attached XDP program to interface %s\n", ifname);
|
||||
|
||||
/* Set up ring buffer polling */
|
||||
rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
|
||||
if (!rb)
|
||||
{
|
||||
fprintf(stderr, "Failed to create ring buffer\n");
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("Start polling ring buffer\n");
|
||||
|
||||
/* Poll the ring buffer */
|
||||
while (1)
|
||||
{
|
||||
err = ring_buffer__poll(rb, -1);
|
||||
if (err == -EINTR)
|
||||
continue;
|
||||
if (err < 0)
|
||||
{
|
||||
fprintf(stderr, "Error polling ring buffer: %d\n", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ring_buffer__free(rb);
|
||||
xdp_tcpdump_bpf__destroy(skel);
|
||||
return -err;
|
||||
}
|
||||
14
42-xdp-loadbalancer/no-docker/xdp_pass.c
Normal file
14
42-xdp-loadbalancer/no-docker/xdp_pass.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
SEC("xdp")
|
||||
int xdp_pass(struct xdp_md* ctx) {
|
||||
void* data = (void*)(long)ctx->data;
|
||||
void* data_end = (void*)(long)ctx->data_end;
|
||||
int pkt_sz = data_end - data;
|
||||
|
||||
bpf_printk("packet size is %d", pkt_sz);
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
char __license[] SEC("license") = "GPL";
|
||||
BIN
42-xdp-loadbalancer/no-docker/xdp_pass.o
Normal file
BIN
42-xdp-loadbalancer/no-docker/xdp_pass.o
Normal file
Binary file not shown.
159
42-xdp-loadbalancer/setup.sh
Executable file
159
42-xdp-loadbalancer/setup.sh
Executable file
@@ -0,0 +1,159 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -xe
|
||||
|
||||
part_mac="DE:AD:BE:EF:00:"
|
||||
|
||||
create_bridge () {
|
||||
if ! ip link show $1 &> /dev/null; then
|
||||
ip link add name $1 type bridge
|
||||
ip link set dev $1 up
|
||||
else
|
||||
echo "Bridge $1 already exists."
|
||||
fi
|
||||
}
|
||||
|
||||
create_pair () {
|
||||
if ! ip link show $1 &> /dev/null; then
|
||||
ip link add name $1 type veth peer name $2
|
||||
ip link set $1 address "$part_mac""$5"
|
||||
ip addr add $3 brd + dev $1
|
||||
ip link set $2 master $4
|
||||
ip link set dev $1 up
|
||||
ip link set dev $2 up
|
||||
else
|
||||
echo "Veth pair $1 <--> $2 already exists."
|
||||
fi
|
||||
}
|
||||
|
||||
create_pair_ns () {
|
||||
if ! ip link show $2 &> /dev/null; then
|
||||
ip link add name $1 type veth peer name $2
|
||||
ip link set $2 master $4
|
||||
ip link set dev $2 up
|
||||
|
||||
ip netns add $5
|
||||
ip link set $1 netns $5
|
||||
ip netns exec $5 ip addr add $3 brd + dev $1
|
||||
ip netns exec $5 ip link set $1 address "$part_mac""$6"
|
||||
ip netns exec $5 ip link set dev $1 up
|
||||
ip netns exec $5 ip link set lo up # Bring up loopback interface
|
||||
else
|
||||
echo "Veth pair $1 <--> $2 already exists in namespace $5."
|
||||
fi
|
||||
}
|
||||
|
||||
# Create bridge br0
|
||||
create_bridge br0
|
||||
|
||||
# Create veth pairs and assign IPs
|
||||
create_pair veth0 veth1 "10.0.0.1/24" br0 01
|
||||
|
||||
# Create veth pairs in namespaces h2, h3, and lb
|
||||
create_pair_ns veth2 veth3 "10.0.0.2/24" br0 h2 02
|
||||
create_pair_ns veth4 veth5 "10.0.0.3/24" br0 h3 03
|
||||
|
||||
# Create the lb namespace
|
||||
create_pair_ns veth6 veth7 "10.0.0.10/24" br0 lb 10
|
||||
|
||||
# Enable IP forwarding on the host
|
||||
sudo sysctl -w net.ipv4.ip_forward=1
|
||||
|
||||
# Set the FORWARD chain policy to ACCEPT in iptables to ensure packets are forwarded
|
||||
sudo iptables -P FORWARD ACCEPT
|
||||
|
||||
# maybe you can do similar things
|
||||
# sudo ip netns exec h2 bpftool load xdp_pass.o veth2
|
||||
# sudo ip netns exec h3 bpftool load xdp_pass.o veth4
|
||||
|
||||
# Helper function for error exit on ping failure
|
||||
function ping_or_fail() {
|
||||
if ! sudo ip netns exec $1 ping -c 3 $2; then
|
||||
echo "Ping from $1 to $2 failed!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Ping test with failure checks
|
||||
function check_connectivity() {
|
||||
echo "Testing connectivity between namespaces and Load Balancer..."
|
||||
|
||||
# Ping from h2 to h3 and h3 to h2
|
||||
ping_or_fail h2 10.0.0.3
|
||||
ping_or_fail h3 10.0.0.2
|
||||
|
||||
# Ping from h2 to Load Balancer and h3 to Load Balancer
|
||||
ping_or_fail h2 10.0.0.10
|
||||
ping_or_fail h3 10.0.0.10
|
||||
|
||||
# Ping from Load Balancer to h2 and h3
|
||||
ping_or_fail lb 10.0.0.2
|
||||
ping_or_fail lb 10.0.0.3
|
||||
|
||||
# Ping from Local Machine to Load Balancer
|
||||
ping -c 3 10.0.0.10 || { echo "Ping from Local Machine to Load Balancer failed!"; exit 1; }
|
||||
|
||||
echo "All ping tests passed!"
|
||||
}
|
||||
|
||||
# Debugging helper functions
|
||||
|
||||
# Check if all interfaces are up and running
|
||||
check_interfaces () {
|
||||
for ns in h2 h3 lb; do
|
||||
echo "Checking interfaces in namespace $ns..."
|
||||
sudo ip netns exec $ns ip addr show
|
||||
sudo ip netns exec $ns ip link show
|
||||
done
|
||||
|
||||
echo "Checking bridge br0..."
|
||||
ip addr show br0
|
||||
ip link show br0
|
||||
}
|
||||
|
||||
# Check IP forwarding settings
|
||||
check_ip_forwarding () {
|
||||
echo "Checking IP forwarding status on the host..."
|
||||
sudo sysctl net.ipv4.ip_forward
|
||||
|
||||
echo "Checking IP forwarding status in namespace $ns..."
|
||||
sudo ip netns exec $ns sysctl net.ipv4.ip_forward
|
||||
}
|
||||
|
||||
# Check ARP table
|
||||
check_arp_table () {
|
||||
echo "Checking ARP table on the host..."
|
||||
arp -n
|
||||
|
||||
for ns in h2 h3 lb; do
|
||||
echo "Checking ARP table in namespace $ns..."
|
||||
sudo ip netns exec $ns ip neigh show
|
||||
done
|
||||
}
|
||||
|
||||
# Check routing tables
|
||||
check_routing_table () {
|
||||
echo "Checking routing table on the host..."
|
||||
ip route show
|
||||
|
||||
for ns in h2 h3 lb; do
|
||||
echo "Checking routing table in namespace $ns..."
|
||||
sudo ip netns exec $ns ip route show
|
||||
done
|
||||
}
|
||||
|
||||
# Check if firewall rules are blocking traffic
|
||||
check_firewall_rules () {
|
||||
echo "Checking firewall rules on the host..."
|
||||
sudo iptables -L
|
||||
}
|
||||
|
||||
# Run checks to verify the network
|
||||
check_interfaces
|
||||
check_ip_forwarding
|
||||
check_arp_table
|
||||
check_routing_table
|
||||
check_firewall_rules
|
||||
check_connectivity
|
||||
|
||||
echo "Setup and checks completed!"
|
||||
36
42-xdp-loadbalancer/teardown.sh
Executable file
36
42-xdp-loadbalancer/teardown.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -xe
|
||||
|
||||
rm_bridge () {
|
||||
if ip link show $1 &> /dev/null; then
|
||||
ip link set dev $1 down
|
||||
ip link delete $1 type bridge
|
||||
fi
|
||||
}
|
||||
|
||||
rm_pair () {
|
||||
if ip link show $1 &> /dev/null; then
|
||||
ip link delete $1 type veth
|
||||
fi
|
||||
}
|
||||
|
||||
rm_ns () {
|
||||
if ip netns list | grep -w "$1" &> /dev/null; then
|
||||
ip netns delete $1
|
||||
fi
|
||||
}
|
||||
|
||||
# Remove bridge br0
|
||||
rm_bridge br0
|
||||
|
||||
# Remove veth pairs
|
||||
rm_pair veth0
|
||||
rm_pair veth2
|
||||
rm_pair veth4
|
||||
rm_pair veth6
|
||||
|
||||
# Remove namespaces
|
||||
rm_ns h2
|
||||
rm_ns h3
|
||||
rm_ns lb
|
||||
117
42-xdp-loadbalancer/xdp_lb.bpf.c
Normal file
117
42-xdp-loadbalancer/xdp_lb.bpf.c
Normal file
@@ -0,0 +1,117 @@
|
||||
// xdp_lb.bpf.c
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/tcp.h>
|
||||
#include "xx_hash.h"
|
||||
|
||||
struct backend_config {
|
||||
__u32 ip;
|
||||
unsigned char mac[ETH_ALEN];
|
||||
};
|
||||
|
||||
// Backend IP and MAC address map
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(max_entries, 2); // Two backends
|
||||
__type(key, __u32);
|
||||
__type(value, struct backend_config);
|
||||
} backends SEC(".maps");
|
||||
|
||||
int client_ip = bpf_htonl(0xa000001);
|
||||
unsigned char client_mac[ETH_ALEN] = {0xDE, 0xAD, 0xBE, 0xEF, 0x0, 0x1};
|
||||
int load_balancer_ip = bpf_htonl(0xa00000a);
|
||||
unsigned char load_balancer_mac[ETH_ALEN] = {0xDE, 0xAD, 0xBE, 0xEF, 0x0, 0x10};
|
||||
|
||||
static __always_inline __u16
|
||||
csum_fold_helper(__u64 csum)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
if (csum >> 16)
|
||||
csum = (csum & 0xffff) + (csum >> 16);
|
||||
}
|
||||
return ~csum;
|
||||
}
|
||||
|
||||
static __always_inline __u16
|
||||
iph_csum(struct iphdr *iph)
|
||||
{
|
||||
iph->check = 0;
|
||||
unsigned long long csum = bpf_csum_diff(0, 0, (unsigned int *)iph, sizeof(struct iphdr), 0);
|
||||
return csum_fold_helper(csum);
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
int xdp_load_balancer(struct xdp_md *ctx) {
|
||||
void *data_end = (void *)(long)ctx->data_end;
|
||||
void *data = (void *)(long)ctx->data;
|
||||
|
||||
bpf_printk("xdp_load_balancer received packet");
|
||||
|
||||
// Ethernet header
|
||||
struct ethhdr *eth = data;
|
||||
if ((void *)(eth + 1) > data_end)
|
||||
return XDP_PASS;
|
||||
|
||||
// Check if the packet is IP (IPv4)
|
||||
if (eth->h_proto != __constant_htons(ETH_P_IP))
|
||||
return XDP_PASS;
|
||||
|
||||
// IP header
|
||||
struct iphdr *iph = (struct iphdr *)(eth + 1);
|
||||
if ((void *)(iph + 1) > data_end)
|
||||
return XDP_PASS;
|
||||
|
||||
// Check if the protocol is TCP or UDP
|
||||
if (iph->protocol != IPPROTO_TCP)
|
||||
return XDP_PASS;
|
||||
|
||||
bpf_printk("Received Source IP: 0x%x", bpf_ntohl(iph->saddr));
|
||||
bpf_printk("Received Destination IP: 0x%x", bpf_ntohl(iph->daddr));
|
||||
bpf_printk("Received Source MAC: %x:%x:%x:%x:%x:%x", eth->h_source[0], eth->h_source[1], eth->h_source[2], eth->h_source[3], eth->h_source[4], eth->h_source[5]);
|
||||
bpf_printk("Received Destination MAC: %x:%x:%x:%x:%x:%x", eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]);
|
||||
|
||||
if (iph->saddr == client_ip)
|
||||
{
|
||||
bpf_printk("Packet from client");
|
||||
|
||||
__u32 key = xxhash32((const char*)iph, sizeof(struct iphdr), 0) % 2;
|
||||
|
||||
struct backend_config *backend = bpf_map_lookup_elem(&backends, &key);
|
||||
if (!backend)
|
||||
return XDP_PASS;
|
||||
|
||||
iph->daddr = backend->ip;
|
||||
__builtin_memcpy(eth->h_dest, backend->mac, ETH_ALEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
bpf_printk("Packet from backend");
|
||||
iph->daddr = client_ip;
|
||||
__builtin_memcpy(eth->h_dest, client_mac, ETH_ALEN);
|
||||
}
|
||||
|
||||
// Update IP source address to the load balancer's IP
|
||||
iph->saddr = load_balancer_ip;
|
||||
// Update Ethernet source MAC address to the current lb's MAC
|
||||
__builtin_memcpy(eth->h_source, load_balancer_mac, ETH_ALEN);
|
||||
|
||||
// Recalculate IP checksum
|
||||
iph->check = iph_csum(iph);
|
||||
|
||||
bpf_printk("Redirecting packet to new IP 0x%x from IP 0x%x",
|
||||
bpf_ntohl(iph->daddr),
|
||||
bpf_ntohl(iph->saddr)
|
||||
);
|
||||
bpf_printk("New Dest MAC: %x:%x:%x:%x:%x:%x", eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]);
|
||||
bpf_printk("New Source MAC: %x:%x:%x:%x:%x:%x\n", eth->h_source[0], eth->h_source[1], eth->h_source[2], eth->h_source[3], eth->h_source[4], eth->h_source[5]);
|
||||
// Return XDP_TX to transmit the modified packet back to the network
|
||||
return XDP_TX;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
96
42-xdp-loadbalancer/xdp_lb.c
Normal file
96
42-xdp-loadbalancer/xdp_lb.c
Normal file
@@ -0,0 +1,96 @@
|
||||
// xdp_lb.c
|
||||
#include <arpa/inet.h>
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <net/if.h>
|
||||
#include "xdp_lb.skel.h" // The generated skeleton
|
||||
|
||||
struct backend_config {
|
||||
__u32 ip;
|
||||
unsigned char mac[6];
|
||||
};
|
||||
|
||||
static int parse_mac(const char *str, unsigned char *mac) {
|
||||
if (sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
||||
&mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 6) {
|
||||
fprintf(stderr, "Invalid MAC address format\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 6) {
|
||||
fprintf(stderr, "Usage: %s <ifname> <backend1_ip> <backend1_mac> <backend2_ip> <backend2_mac>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *ifname = argv[1];
|
||||
struct backend_config backend[2];
|
||||
|
||||
// Parse backend 1
|
||||
if (inet_pton(AF_INET, argv[2], &backend[0].ip) != 1) {
|
||||
fprintf(stderr, "Invalid backend 1 IP address\n");
|
||||
return 1;
|
||||
}
|
||||
if (parse_mac(argv[3], backend[0].mac) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse backend 2
|
||||
if (inet_pton(AF_INET, argv[4], &backend[1].ip) != 1) {
|
||||
fprintf(stderr, "Invalid backend 2 IP address\n");
|
||||
return 1;
|
||||
}
|
||||
if (parse_mac(argv[5], backend[1].mac) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Load and attach the BPF program
|
||||
struct xdp_lb_bpf *skel = xdp_lb_bpf__open_and_load();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "Failed to open and load BPF skeleton\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ifindex = if_nametoindex(ifname);
|
||||
if (ifindex < 0) {
|
||||
perror("if_nametoindex");
|
||||
xdp_lb_bpf__destroy(skel);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bpf_program__attach_xdp(skel->progs.xdp_load_balancer, ifindex) < 0) {
|
||||
fprintf(stderr, "Failed to attach XDP program\n");
|
||||
xdp_lb_bpf__destroy(skel);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Update backend configurations
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (bpf_map_update_elem(bpf_map__fd(skel->maps.backends), &i, &backend[i], 0) < 0) {
|
||||
perror("bpf_map_update_elem");
|
||||
xdp_lb_bpf__destroy(skel);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
printf("XDP load balancer configured with backends:\n");
|
||||
printf("Backend 1 - IP: %s, MAC: %s\n", argv[2], argv[3]);
|
||||
printf("Backend 2 - IP: %s, MAC: %s\n", argv[4], argv[5]);
|
||||
|
||||
printf("Press Ctrl+C to exit...\n");
|
||||
while (1) {
|
||||
sleep(1); // Keep the program running
|
||||
}
|
||||
|
||||
// Cleanup and detach
|
||||
bpf_xdp_detach(ifindex, 0, NULL);
|
||||
xdp_lb_bpf__detach(skel);
|
||||
xdp_lb_bpf__destroy(skel);
|
||||
return 0;
|
||||
}
|
||||
57
42-xdp-loadbalancer/xx_hash.h
Normal file
57
42-xdp-loadbalancer/xx_hash.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef XXHASH_BPF_H
|
||||
#define XXHASH_BPF_H
|
||||
|
||||
#define PRIME1 0x9E3779B1U
|
||||
#define PRIME2 0x85EBCA77U
|
||||
#define PRIME3 0xC2B2AE3DU
|
||||
#define PRIME4 0x27D4EB2FU
|
||||
#define PRIME5 0x165667B1U
|
||||
|
||||
static __always_inline unsigned int rotl (unsigned int x, int r) {
|
||||
return ((x << r) | (x >> (32 - r)));
|
||||
}
|
||||
// Normal stripe processing routine.
|
||||
static __always_inline unsigned int round_xxhash(unsigned int acc, const unsigned int input) {
|
||||
return rotl(acc + (input * PRIME2), 13) * PRIME1;
|
||||
}
|
||||
|
||||
static __always_inline unsigned int avalanche_step (const unsigned int h, const int rshift, const unsigned int prime) {
|
||||
return (h ^ (h >> rshift)) * prime;
|
||||
}
|
||||
// Mixes all bits to finalize the hash.
|
||||
static __always_inline unsigned int avalanche (const unsigned int h) {
|
||||
return avalanche_step(avalanche_step(avalanche_step(h, 15, PRIME2), 13, PRIME3), 16, 1);
|
||||
}
|
||||
|
||||
static __always_inline unsigned int endian32 (const char *v) {
|
||||
return (unsigned int)((unsigned char)(v[0]))|((unsigned int)((unsigned char)(v[1])) << 8)
|
||||
|((unsigned int)((unsigned char)(v[2])) << 16)|((unsigned int)((unsigned char)(v[3])) << 24);
|
||||
}
|
||||
|
||||
static __always_inline unsigned int fetch32 (const char *p, const unsigned int v) {
|
||||
return round_xxhash(v, endian32(p));
|
||||
}
|
||||
|
||||
// Processes the last 0-15 bytes of p.
|
||||
static __always_inline unsigned int finalize (const unsigned int h, const char *p, unsigned int len) {
|
||||
return
|
||||
(len >= 4) ? finalize(rotl(h + (endian32(p) * PRIME3), 17) * PRIME4, p + 4, len - 4) :
|
||||
(len > 0) ? finalize(rotl(h + ((unsigned char)(*p) * PRIME5), 11) * PRIME1, p + 1, len - 1) :
|
||||
avalanche(h);
|
||||
}
|
||||
|
||||
static __always_inline unsigned int h16bytes_4 (const char *p, unsigned int len, const unsigned int v1, const unsigned int v2, const unsigned int v3, const unsigned int v4) {
|
||||
return
|
||||
(len >= 16) ? h16bytes_4(p + 16, len - 16, fetch32(p, v1), fetch32(p+4, v2), fetch32(p+8, v3), fetch32(p+12, v4)) :
|
||||
rotl(v1, 1) + rotl(v2, 7) + rotl(v3, 12) + rotl(v4, 18);
|
||||
}
|
||||
|
||||
static __always_inline unsigned int h16bytes_3 (const char *p, unsigned int len, const unsigned int seed) {
|
||||
return h16bytes_4(p, len, seed + PRIME1 + PRIME2, seed + PRIME2, seed, seed - PRIME1);
|
||||
}
|
||||
|
||||
static __always_inline unsigned int xxhash32 (const char *input, unsigned int len, unsigned int seed) {
|
||||
return finalize((len >= 16 ? h16bytes_3(input, len, seed) : seed + PRIME5) + len, (input) + (len & ~0xF), len & 0xF);
|
||||
}
|
||||
|
||||
#endif
|
||||
99
print.html
99
print.html
@@ -219,12 +219,11 @@
|
||||
<h3 id="了解如何开发-ebpf-程序10-15h"><a class="header" href="#了解如何开发-ebpf-程序10-15h">了解如何开发 eBPF 程序(10-15h)</a></h3>
|
||||
<p>了解并尝试一下 eBPF 开发框架:</p>
|
||||
<ul>
|
||||
<li>bpftrace 教程,对于最简单的应用来说,bpftrace 可能是最方便的:https://eunomia.dev/zh/tutorials/bpftrace-tutorial/ (试试,1h)</li>
|
||||
<li>bpftrace 教程,对于最简单的应用来说,bpftrace 可能是最方便的:<a href="https://eunomia.dev/zh/tutorials/bpftrace-tutorial/">https://eunomia.dev/zh/tutorials/bpftrace-tutorial/</a> (试试,1h)</li>
|
||||
<li>BCC 开发各类小工具的例子:<a href="https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md">https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md</a> (跑一遍,3-4h)</li>
|
||||
<li>libbpf 的一些例子:<a href="https://github.com/libbpf/libbpf-bootstrap">https://github.com/libbpf/libbpf-bootstrap</a> (选感兴趣的运行一下,并阅读一下源代码,2h)</li>
|
||||
<li>基于 libbpf 和 eunomia-bpf 的教程:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> (阅读 1-10 的部分,3-4h)</li>
|
||||
<li>基于 C 语言 libbpf, Go 语言或者 Rust 语言和 eunomia-bpf 的教程:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> (阅读 1-20 的部分,3-8h)</li>
|
||||
</ul>
|
||||
<p>其他开发框架:Go 语言或者 Rust 语言,请自行搜索并且尝试(0-2h)</p>
|
||||
<p>有任何问题或者想了解的东西,不管是不是和本项目相关,都可以在本项目的 discussions 里面开始讨论。</p>
|
||||
<p>回答一些问题,并且进行一些尝试(2-5h):</p>
|
||||
<ol>
|
||||
@@ -285,15 +284,13 @@ eBPF 程序每次执行时候都需要进行编译,编译则需要用户配置
|
||||
字节码装载到内核中。同样的,<code>libbpf-bootstrap</code> 也有非常完备的入门教程,用户可以在<a href="https://nakryiko.com/posts/libbpf-bootstrap/">该处</a>
|
||||
得到详细的入门操作介绍。</p>
|
||||
<h3 id="eunomia-bpf"><a class="header" href="#eunomia-bpf">eunomia-bpf</a></h3>
|
||||
<p>开发、构建和分发 eBPF 一直以来都是一个高门槛的工作,使用 BCC、bpftrace 等工具开发效率高、可移植性好,但是分发部署时需要安装 LLVM、Clang 等编译环境,每次运行的时候执行本地或远程编译过程,资源消耗较大;使用原生的 CO-RE libbpf 时又需要编写不少用户态加载代码来帮助 eBPF 程序正确加载和从内核中获取上报的信息,同时对于 eBPF 程序的分发、管理也没有很好地解决方案。</p>
|
||||
<p><a href="https://github.com/eunomia-bpf/eunomia-bpf">eunomia-bpf</a> 是一个开源的 eBPF 动态加载运行时和开发工具链,是为了简化 eBPF 程序的开发、构建、分发、运行而设计的,基于 libbpf 的 CO-RE 轻量级开发框架。</p>
|
||||
<p>使用 eunomia-bpf ,可以:</p>
|
||||
<ul>
|
||||
<li>在编写 eBPF 程序或工具时只编写内核态代码,自动获取内核态导出信息,并作为模块动态加载;</li>
|
||||
<li>使用 WASM 进行用户态交互程序的开发,在 WASM 虚拟机内部控制整个 eBPF 程序的加载和执行,以及处理相关数据;</li>
|
||||
<li>eunomia-bpf 可以将预编译的 eBPF 程序打包为通用的 JSON 或 WASM 模块,跨架构和内核版本进行分发,无需重新编译即可动态加载运行。</li>
|
||||
</ul>
|
||||
<p>eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的 BCC、原生 libbpf 等框架,大幅简化了 eBPF 程序的开发流程,在大多数时候只需编写内核态代码,即可轻松构建、打包、发布完整的 eBPF 应用,同时内核态 eBPF 代码保证和主流的 libbpf,libbpfgo,libbpf-rs 等开发框架的 100% 兼容性。需要编写用户态代码的时候,也可以借助 Webassembly 实现通过多种语言进行用户态开发。和 bpftrace 等脚本工具相比, eunomia-bpf 保留了类似的便捷性, 同时不仅局限于 trace 方面, 可以用于更多的场景, 如网络、安全等等。</p>
|
||||
<p>eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的 BCC、原生 libbpf 等框架,简化了 eBPF 程序的开发流程,在大多数时候只需编写内核态代码,即可轻松构建、打包、发布完整的 eBPF 应用。</p>
|
||||
<blockquote>
|
||||
<ul>
|
||||
<li>eunomia-bpf 项目 Github 地址: <a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a></li>
|
||||
@@ -404,7 +401,7 @@ Packing ebpf object and config into package.json...
|
||||
</code></pre>
|
||||
<p>然后使用 ecli 运行编译后的程序:</p>
|
||||
<pre><code class="language-console">$ sudo ./ecli run package.json
|
||||
Runing eBPF program...
|
||||
Running eBPF program...
|
||||
</code></pre>
|
||||
<p>运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:</p>
|
||||
<pre><code class="language-console">$ sudo cat /sys/kernel/debug/tracing/trace_pipe | grep "BPF triggered sys_enter_write"
|
||||
@@ -4702,19 +4699,81 @@ Packing ebpf object and config into package.json...
|
||||
<li><a href="http://just4coding.com/2022/08/05/tc/">http://just4coding.com/2022/08/05/tc/</a></li>
|
||||
<li><a href="https://arthurchiao.art/blog/understanding-tc-da-mode-zh/">https://arthurchiao.art/blog/understanding-tc-da-mode-zh/</a></li>
|
||||
</ul>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="ebpf-入门实践教程二十一使用-xdp-实现可编程包处理"><a class="header" href="#ebpf-入门实践教程二十一使用-xdp-实现可编程包处理">eBPF 入门实践教程二十一:使用 xdp 实现可编程包处理</a></h1>
|
||||
<h2 id="背景-3"><a class="header" href="#背景-3">背景</a></h2>
|
||||
<p>xdp(eXpress Data Path)是 Linux 内核中新兴的一种绕过内核的、可编程的包处理方案。相较于 cBPF,xdp 的挂载点非常底层,位于网络设备驱动的软中断处理过程,甚至早于 skb_buff 结构的分配。因此,在 xdp 上挂载 eBPF 程序适用于很多简单但次数极多的包处理操作(如防御 Dos 攻击),可以达到很高的性能(24Mpps/core)。</p>
|
||||
<h2 id="xdp-概述"><a class="header" href="#xdp-概述">XDP 概述</a></h2>
|
||||
<p>xdp 不是第一个支持可编程包处理的系统,在此之前,以 DPDK(Data Plane Development Kit)为代表的内核旁路方案甚至能够取得更高的性能,其思路为完全绕过内核,由用户态的网络应用接管网络设备,从而避免了用户态和内核态的切换开销。然而,这样的方式具有很多天然的缺陷:</p>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="ebpf-入门实践教程二十一-使用-xdp-进行可编程数据包处理"><a class="header" href="#ebpf-入门实践教程二十一-使用-xdp-进行可编程数据包处理">eBPF 入门实践教程二十一: 使用 XDP 进行可编程数据包处理</a></h1>
|
||||
<p>在本教程中,我们将介绍 XDP(eXpress Data Path),并通过一个简单的例子帮助你入门。之后,我们将探讨更高级的 XDP 应用,例如负载均衡器、防火墙及其他实际应用。如果你对 eBPF 或 XDP 感兴趣,请在 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">Github</a> 上为我们点赞!</p>
|
||||
<h2 id="什么是-xdp"><a class="header" href="#什么是-xdp">什么是 XDP?</a></h2>
|
||||
<p>XDP 是 Linux 内核中的一种高性能可编程数据路径,专为网络接口级的数据包处理而设计。通过将 eBPF 程序直接附加到网络设备驱动程序上,XDP 能够在数据包到达内核网络栈之前拦截并处理它们。这使得 XDP 能够进行极低延迟和高效的数据包处理,非常适合如 DDoS 防护、负载均衡和流量过滤等任务。实际上,XDP 每核心的吞吐量可以高达 <strong>每秒 2400 万包(Mpps)</strong>。</p>
|
||||
<h3 id="为什么选择-xdp"><a class="header" href="#为什么选择-xdp">为什么选择 XDP?</a></h3>
|
||||
<p>XDP 运行在比传统 Linux 网络组件(如 cBPF)更低的层级,在网络设备驱动程序的软中断上下文中执行。它能够在数据包被内核标准网络栈处理之前对其进行处理,避免了创建 Linux 中表示网络数据包的 <code>skb_buff</code> 结构。这种早期处理为简单但频繁的操作(如丢弃恶意数据包或负载均衡服务器)带来了显著的性能提升。</p>
|
||||
<p>与其他数据包处理机制相比,XDP 在性能和可用性之间取得了平衡,它利用了 Linux 内核的安全性和可靠性,同时通过可编程的 eBPF 提供了灵活性。</p>
|
||||
<h2 id="xdp-与其他方法的比较"><a class="header" href="#xdp-与其他方法的比较">XDP 与其他方法的比较</a></h2>
|
||||
<p>在 XDP 出现之前,一些解决方案通过完全绕过内核来加速数据包处理。其中一个显著的例子是 <strong>DPDK</strong>(数据平面开发工具包)。DPDK 允许用户空间应用程序直接控制网络设备,从而实现非常高的性能。然而,这种方法也存在一些权衡:</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p><strong>缺乏内核集成</strong>:DPDK 及其他内核绕过解决方案无法利用现有的内核网络功能,开发者必须在用户空间重新实现许多协议和功能。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>安全边界</strong>:这些绕过技术破坏了内核的安全模型,使得难以利用内核提供的安全工具。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>用户空间与内核的转换开销</strong>:当用户空间数据包处理需要与传统内核网络交互时(例如基于套接字的应用程序),数据包必须重新注入到内核中,增加了开销和复杂性。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>专用 CPU 使用</strong>:为了处理高流量,DPDK 和类似解决方案通常需要专用的 CPU 核心来处理数据包,这限制了通用系统的可扩展性和效率。</p>
|
||||
</li>
|
||||
</ol>
|
||||
<p>另一个替代 XDP 的方法是使用 Linux 网络栈中的 <strong>内核模块</strong> 或 <strong>挂钩</strong>。虽然这种方法可以很好地集成现有的内核功能,但它需要大量的内核修改,且由于在数据包处理管道的后期运行,无法提供与 XDP 相同的性能优势。</p>
|
||||
<h3 id="xdp--ebpf-的优势"><a class="header" href="#xdp--ebpf-的优势">XDP + eBPF 的优势</a></h3>
|
||||
<p>XDP 与 eBPF 结合提供了介于内核绕过方案(如 DPDK)和内核集成方案之间的中间地带。以下是 XDP + eBPF 脱颖而出的原因:</p>
|
||||
<ul>
|
||||
<li>无法与内核中成熟的网络模块集成,而不得不在用户态将其重新实现;</li>
|
||||
<li>破坏了内核的安全边界,使得内核提供的很多网络工具变得不可用;</li>
|
||||
<li>在与常规的 socket 交互时,需要从用户态重新将包注入到内核;</li>
|
||||
<li>需要占用一个或多个单独的 CPU 来进行包处理;</li>
|
||||
<li>
|
||||
<p><strong>高性能</strong>:通过在网络接口卡(NIC)驱动程序级别拦截数据包,XDP 可以实现接近线速的性能,用于丢弃、重定向或负载均衡数据包,同时保持低资源消耗。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>内核集成</strong>:与 DPDK 不同,XDP 在 Linux 内核中工作,允许与现有的内核网络栈和工具(如 <code>iptables</code>、<code>nftables</code> 或套接字)无缝交互。无需在用户空间重新实现网络协议。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>安全性</strong>:eBPF 虚拟机确保用户定义的 XDP 程序是被隔离的,不会对内核造成不稳定影响。eBPF 的安全模型防止恶意或有缺陷的代码损害系统,提供了一个安全的可编程数据包处理环境。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>不需要专用 CPU</strong>:XDP 允许数据包处理而无需将整个 CPU 核心专用于网络任务。这提高了系统的整体效率,允许更灵活的资源分配。</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>总的来说,XDP + eBPF 提供了一种强大的可编程数据包处理解决方案,结合了高性能与内核集成的灵活性和安全性。它消除了完全绕过内核方案的缺点,同时保留了内核安全性和功能的优势。</p>
|
||||
<h2 id="xdp-的项目和应用案例"><a class="header" href="#xdp-的项目和应用案例">XDP 的项目和应用案例</a></h2>
|
||||
<p>XDP 已经在许多高调的项目中得到应用,这些项目展示了它在实际网络场景中的强大功能和灵活性:</p>
|
||||
<h3 id="1-cilium"><a class="header" href="#1-cilium">1. <strong>Cilium</strong></a></h3>
|
||||
<ul>
|
||||
<li><strong>描述</strong>:Cilium 是一个为云原生环境(尤其是 Kubernetes)设计的开源网络、安全和可观测性工具。它利用 XDP 实现高性能的数据包过滤和负载均衡。</li>
|
||||
<li><strong>应用案例</strong>:Cilium 将数据包过滤和安全策略卸载到 XDP,实现高吞吐量和低延迟的容器化环境流量管理,同时不牺牲可扩展性。</li>
|
||||
<li><strong>链接</strong>:<a href="https://cilium.io/">Cilium</a></li>
|
||||
</ul>
|
||||
<h3 id="2-katran"><a class="header" href="#2-katran">2. <strong>Katran</strong></a></h3>
|
||||
<ul>
|
||||
<li><strong>描述</strong>:Katran 是由 Facebook 开发的第 4 层负载均衡器,优化了高可扩展性和性能。它使用 XDP 处理数据包转发,开销极小。</li>
|
||||
<li><strong>应用案例</strong>:Katran 每秒处理数百万个数据包,高效地将流量分配到后端服务器上,利用 XDP 在大规模数据中心中实现低延迟和高性能的负载均衡。</li>
|
||||
<li><strong>链接</strong>:<a href="https://github.com/facebookincubator/katran">Katran GitHub</a></li>
|
||||
</ul>
|
||||
<h3 id="3-cloudflare-的-xdp-ddos-保护"><a class="header" href="#3-cloudflare-的-xdp-ddos-保护">3. <strong>Cloudflare 的 XDP DDoS 保护</strong></a></h3>
|
||||
<ul>
|
||||
<li><strong>描述</strong>:Cloudflare 已经实现了基于 XDP 的实时 DDoS 缓解。通过在 NIC 级别处理数据包,Cloudflare 能够在恶意流量进入网络栈之前过滤掉攻击流量,最小化 DDoS 攻击对其系统的影响。</li>
|
||||
<li><strong>应用案例</strong>:Cloudflare 利用 XDP 在管道早期丢弃恶意数据包,保护其基础设施免受大规模 DDoS 攻击,同时保持对合法流量的高可用性。</li>
|
||||
<li><strong>链接</strong>:<a href="https://blog.cloudflare.com/l4drop-xdp-ebpf-based-ddos-mitigations/">Cloudflare 博客关于 XDP</a></li>
|
||||
</ul>
|
||||
<p>这些项目展示了 XDP 在不同领域的可扩展和高效的数据包处理能力,从安全和负载均衡到云原生网络。</p>
|
||||
<h3 id="为什么选择-xdp-而不是其他方法"><a class="header" href="#为什么选择-xdp-而不是其他方法">为什么选择 XDP 而不是其他方法?</a></h3>
|
||||
<p>与传统方法(如 <code>iptables</code>、<code>nftables</code> 或 <code>tc</code>)相比,XDP 提供了几个明显的优势:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>速度与低开销</strong>:XDP 直接在 NIC 驱动程序中运行,绕过了内核的大部分开销,使数据包处理更快。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>可定制性</strong>:XDP 允许开发人员通过 eBPF 创建自定义的数据包处理程序,提供比传统工具(如 <code>iptables</code>)更大的灵活性和细粒度控制。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>资源效率</strong>:XDP 不需要像 DPDK 等用户空间解决方案那样将整个 CPU 核心专用于数据包处理,因此它是高性能网络的更高效选择。</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>除此之外,利用内核模块和内核网络协议栈中的 hook 点也是一种思路,然而前者对内核的改动大,出错的代价高昂;后者在整套包处理流程中位置偏后,其效率不够理想。</p>
|
||||
<p>总而言之,xdp + eBPF 为可编程包处理系统提出了一种更为稳健的思路,在某种程度上权衡了上述方案的种种优点和不足,获取较高性能的同时又不会对内核的包处理流程进行过多的改变,同时借助 eBPF 虚拟机的优势将用户定义的包处理过程进行隔离和限制,提高了安全性。</p>
|
||||
<h2 id="编写-ebpf-程序-3"><a class="header" href="#编写-ebpf-程序-3">编写 eBPF 程序</a></h2>
|
||||
<pre><code class="language-C">#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
@@ -4772,12 +4831,12 @@ Packing ebpf object and config into package.json...
|
||||
<li><a href="http://arthurchiao.art/blog/linux-net-stack-implementation-rx-zh/">http://arthurchiao.art/blog/linux-net-stack-implementation-rx-zh/</a></li>
|
||||
<li><a href="https://github.com/xdp-project/xdp-tutorial/tree/master/basic01-xdp-pass">https://github.com/xdp-project/xdp-tutorial/tree/master/basic01-xdp-pass</a></li>
|
||||
</ul>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="在-andorid-上使用-ebpf-程序"><a class="header" href="#在-andorid-上使用-ebpf-程序">在 Andorid 上使用 eBPF 程序</a></h1>
|
||||
<div style="break-before: page; page-break-before: always;"></div><h1 id="在-android-上使用-ebpf-程序"><a class="header" href="#在-android-上使用-ebpf-程序">在 Android 上使用 eBPF 程序</a></h1>
|
||||
<blockquote>
|
||||
<p>本文主要记录了笔者在 Android Studio Emulator 中测试高版本 Android Kernel 对基于 libbpf 的 CO-RE 技术支持程度的探索过程、结果和遇到的问题。
|
||||
测试采用的方式是在 Android Shell 环境下构建 Debian 环境,并基于此尝试构建 eunomia-bpf 工具链、运行其测试用例。</p>
|
||||
</blockquote>
|
||||
<h2 id="背景-4"><a class="header" href="#背景-4">背景</a></h2>
|
||||
<h2 id="背景-3"><a class="header" href="#背景-3">背景</a></h2>
|
||||
<p>截至目前(2023-04),Android 还未对 eBPF 程序的动态加载做出较好的支持,无论是以 bcc 为代表的带编译器分发方案,还是基于 btf 和 libbpf 的 CO-RE 方案,都在较大程度上离不开 Linux 环境的支持,无法在 Android 系统上很好地运行<sup class="footnote-reference"><a href="#WeiShu">1</a></sup>。</p>
|
||||
<p>虽然如此,在 Android 平台上尝试 eBPF 也已经有了一些成功案例,除谷歌官方提供的修改 <code>Android.bp</code> 以将 eBPF 程序随整个系统一同构建并挂载的方案<sup class="footnote-reference"><a href="#Google">2</a></sup>,也有人提出基于 Android 内核构建 Linux 环境进而运行 eBPF 工具链的思路,并开发了相关工具。</p>
|
||||
<p>目前已有的资料,大多基于 adeb/eadb 在 Android 内核基础上构建 Linux 沙箱,并对 bcc 和 bpftrace 相关工具链进行测试,而对 CO-RE 方案的测试工作较少。在 Android 上使用 bcc 工具目前有较多参考资料,如:</p>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
158719
third_party/vmlinux/vmlinux.h
vendored
158719
third_party/vmlinux/vmlinux.h
vendored
File diff suppressed because it is too large
Load Diff
158719
third_party/vmlinux/x86/vmlinux.h
vendored
158719
third_party/vmlinux/x86/vmlinux.h
vendored
File diff suppressed because it is too large
Load Diff
158719
third_party/vmlinux/x86/vmlinux_601.h
vendored
158719
third_party/vmlinux/x86/vmlinux_601.h
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user