高速网络包处理,基础网络协议上内核态直接处理数据包,XDP技术的原理
文章目录
- 预备知识
- TCP/IP 网络模型(4层、7层)
- iptables/netfilter
- linux网络为什么慢
- DPDK
- XDP
- BFP
- eBPF
- XDP
- XDP 程序典型执行流
- 通过网络协议栈的入包
- XDP 组成
- 使用 GO 编写 XDP 程序
- 明确流程
- 选择eBPF库
- 编写eBPF代码
- 编写Go代码
- 动态更新黑名单
预备知识
TCP/IP 网络模型(4层、7层)
TCP/IP网络模型
- 链路层:负责封装和解封装IP报文,发送和接受ARP/RARP报文等。
- 网络层:负责路由以及把分组报文发送给目标网络或主机。
- 传输层:负责对报文进行分组和重组,并以TCP或UDP协议格式封装报文。
- 应用层:负责向用户提供应用程序,比如HTTP、FTP、Telnet、DNS、SMTP等。



数据封装

iptables/netfilter
iptables是一个配置Linux内核防火墙的命令行工具,它基于内核的netfilter机制。
新版本的内核(3.13+)也提供了nftables,用于取代iptables

iptables规则逐渐增加,遍历iptables效率变得很低,一个表现就是kube-proxy,他是Kubernetes的一个组件,容器要使用iptables和-j DNAT规则为服务提供负载均衡。随着服务增加,iptable的规则列表指数增长。随着服务数量的增长,网络延迟和性能严重下降。iptables的还有一个缺点,无法实现增量更新。每次添加新规则时,必须更新整个规则列表。
一个例子:装配2万个Kubernetes服务产生16万条的iptables规则需要耗时5个小时。
在容器环境下还有一个问题:容器的生命周期可能很多,可能一个容器的生命周期只有几秒,意味着iptables规则需要被快速更新,这也使得依靠使用IP地址进行安全过滤的系统受到压力,因为集群中的所有节点都必须始终知道最新的IP到容器的映射。
一个解决方案是BPF,Cilium项目就利用了这种技术.

利用BPF构建的bpfilter性能远高于iptables和nftables, linux内核社区的Florian Westphal提出了一个运行在bpfilter上框架,通过框架并将nftables转换为BPF。框架允许保持特定领域nftables语句,而且还可以带有JIT编译器,硬件卸载和工具集等BPF运行时的所有优点。
linux网络为什么慢
linux协议栈是在20世纪90年代作为一个通用操作系统实现的,想要支持现代的高速网络,必须要做优化.
dog250 把linux协议栈重新"分层", 指出了其中的"门", 即那些会严重影响性能的门槛

目前有两个比较火的方案:DPDK和XDP,两种方案分别在用户层和内核层直接处理数据包,避免了用户、内核态切换k开销。
DPDK
DPDK由intel支持,DPDK的加速方案原理是完全绕开内核实现的协议栈,把数据包直接从网卡拉到用户态,依靠Intel自身处理器的一些专门优化,来高速处理数据包。
Intel DPDK全称Intel Data Plane Development Kit,是intel提供的数据平面开发工具集,为Intel architecture(IA)处理器架构下用户空间高效的数据包处理提供库函数和驱动的支持,它不同于Linux系统以通用性设计为目的,而是专注于网络应用中数据包的高性能处理。
DPDK应用程序是运行在用户空间上利用自身提供的数据平面库来收发数据包,绕过了Linux内核协议栈对数据包处理过程。Linux内核将DPDK应用程序看作是一个普通的用户态进程,包括它的编译、连接和加载方式和普通程序没有什么两样。DPDK程序启动后只能有一个主线程,然后创建一些子线程并绑定到指定CPU核心上运行。

在现有的所有框架中,内核旁路方式性能是最高的,这种方式在管理、维护和安全方面都存在不足。
XDP 采用了一种与内核旁路截然相反的方式:相比于将网络硬件的控制权上移到用户空间, XDP 将性能攸关的包处理操作直接放在内核中,在操作系统的网络栈之前执行。
这种方式同样避免了内核-用户态切换开销(所有操作都在内核);
但仍然由内核来管理硬件,因此保留了操作系统提供的管理接口和安全防护能力;
这里的主要创新是:使用了一个虚拟执行环境,它能对加载的 程序进行校验,确保它们不会对内核造成破坏。
XDP
BFP
BPF (Berkeley Packet Filter) 是一个非常高效的网络包过滤机制,它的目标是避免不必要的用户空间申请。它直接在内核空间处理网络数据包。 BPF 支持的最常见的应用就是 tcpdump 工具中使用的过滤器表达式。在 tcpdump 中,表达式被编译转换为 BPF 的字节码。内核加载这些字节码并且用在原始网络包流中,以此来高效的把符合过滤条件的数据包发送到用户空间。
eBPF
eBPF(extended Berkeley Packet Filter)起源于BPF,它提供了内核的数据包过滤机制。
- BPF is a highly flexible and efficient virtual machine-like construct in the Linux kernel allowing to execute bytecode at various hook points in a safe manner. It is used in a number of Linux kernel subsystems, most prominently networking, tracing and security (e.g. sandboxing).
- BPF的最原始版本为cBPF,曾用于tcpdump
- Berkeley Packet Filter 尽管名字的也表明最初是设计用于packet filtering,但是现在已经远不止networking上面的用途了.

基于bpf 这个项目 开发了很多有用的小工具, 具体如下图

eBPF 是对 Linux 观测系统 BPF 的扩展和加强版本。可以把它看作是 BPF 的同类。有了 eBPF 就可以自定义沙盒中的字节码,这个沙盒是 eBPF 在内核中提供的,可以在内核中安全的执行几乎所有内核符号表抛出的函数,而不用担心搞坏内核。实际上,eBPF 也是加强了在和用户空间交互的安全性。在内核中的检测器会拒绝加载引用了无效指针的字节码或者是以达到最大栈大小限制。循环也是不允许的(除非在编译时就知道是有常数上线的循环),字节码只能够调用一小部分指定的 eBPF 帮助函数。eBPF 程序保证能及时终止,避免耗尽系统资源,而这种情况出现在内核模块执行中,内核模块会造成内核的不稳定和可怕的内核奔溃。相反的,你可能会发现和内核模块提供的自由度来比,eBPF有太多限制了,但是综合考虑下来还是更倾向于 eBPF,而不是面向模块的代码,主要是基于授权后的 eBPF 不会对内核造成损害。然而这还不是它唯一的优势。
XDP操作模式
XDP支持3种工作模式,默认使用native模式:
- Native XDP:在native模式下,XDP BPF程序运行在网络驱动的早期接收路径上(RX队列),因此,使用该模式时需要网卡驱动程序支持。
- Offloaded XDP:在Offloaded模式下,XDP BFP程序直接在NIC(Network Interface Controller)中处理数据包,而不使用主机CPU,相比native模式,性能更高
- Generic XDP:Generic模式主要提供给开发人员测试使用,对于网卡或驱动无法支持native或offloaded模式的情况,内核提供了通用的generic模式,运行在协议栈中,不需要对驱动做任何修改。生产环境中建议使用native或offloaded模式

XDP
XDP的意思是eXpress Data Path,它能够在网络包进入用户态直接对网络包进行过滤或者处理。XDP依赖eBPF技术。
XDP 或 Express Data Path 的兴起是因为 Linux 内核需要一个高性能的包处理能力。很多绕过内核的技术(DPDK是最突出的一个)目标都是通过把包处理迁移到用户空间来加速网络操作。
这就意味着要消除内核-用户空间边界之间的上下文切换、系统调用转换或 IRQ 请求所引起的开销。操作系统将网络堆栈的控制权交给用户空间进程,这些进程通过自己的驱动程序直接与 NIC 交互。
虽然这种做法的带来了明显的高性能,但是它也带来了一系列的缺陷,包括在用户空间要重新实现 TCP/IP 协议栈以及其它网络功能,或者是放弃了内核中强大的资源抽象管理和安全管理。
XDP 的目的是在内核中也达到可编程的包处理,并且仍然保留基础的网络协议栈模块。实际上,XDP 代表了 eBPF 指令的自然扩展能力。它使用 maps,可管理的帮助函数,沙箱字节运行器来做到可编程,这些字节码会被检测安全之后才会加载到内核中运行。
XDP 高速处理路径的关键点在于这些编程字节码被加载到网络协议栈最早期的可能处理点上,就在网络包接受队列(RX)之后。在网络协议栈的这一阶段中,还没有构建网络包的任何内核属性,所以非常有利于提升网络处理速度。


相对于DPDK,XDP具有以下优点
- 无需第三方代码库和许可
- 同时支持轮询式和中断式网络
- 无需分配大页
- 无需专用的CPU
- 无需定义新的安全网络模型
XDP的使用场景包括
- DDoS防御
- 防火墙
- 基于XDP_TX的负载均衡
- 网络统计
- 复杂网络采样
- 高速交易平台
为了强调 XDP 在网络协议栈中的位置,让我们来一起看看一个 TCP 包的生命过程,从它到达 NIC 知道它发送到用户空间的目的 socket。始终要记住这是一个高级别的视图。我们将只触及这个复杂的核心网络堆栈的表面层。
XDP 程序典型执行流
下图是一个典型的 XDP 程序执行流:

网卡收到一个包时,XDP程序依次执行:
- 提取包头中的信息(例如 IP、MAC、Port、Proto 等),
执行到程序时,系统会传递给它一个上下文对象(context object)作为参赛 (即 struct xdp_md *ctx,后面有例子),其中包括了指向原 始包数据的指针,以及描述这个包是从哪个网卡的哪个接口接收上来的等元数据字段。
- 读取或更新一些资源的元信息(例如更新统计信息);
解析包数据之后,XDP 程序可以读取 ctx 中的包元数据(packet metadata) 字段,例如从哪个网卡的哪个接口收上来的(ifindex)。除此之外,ctx 对象还允许 程序访问与包数据毗邻的一块特殊内存区域(cb, control buffer), 在包穿越整个系统的过程中,可以将自定义的数据塞在这里。
除了 per-packet metadata,XDP 程序还可以通过 BPF map 定义和访问自己的持久数据 ,以及通过各种 helper 函数访问内核基础设施。
BPF map 使 BPF 程序能与系统的其他部分之间通信;
Helpers 使 BPF 程序能利用到某些已有的内核功能(例如路由表), 而无需穿越整个内核网络栈。
如果有需要,对这个包进行 rewrite header 操作,
程序能修改包数据的任何部分,包括添加或删除包头。这使得 XDP 程序能执行封装/接封装操作,以及重写(rewrite)地址字段然后转发等操作。
内核 helper 函数各有不同用途,例如修改一个包之后,计算新的校验和(checksum)。
进行最后的判决(verdict),确定接下来对这个包执行什么操作;
程序还能通过尾调用(tail call),将控制权交给另一个 XDP 程序; 通过这种方式,可以将一个大程序拆分成几个逻辑上的小程序(例如,根据 IPv4/IPv6)。
由于 XDP 程序可包含任意指令,因此前三步(读取包数据、处理元数据、重写包数据) 顺序可以是任意的,而且支持多层嵌套。 但实际中为了获得高性能,大部分情况下还是将执行结构组织成这顺序的三步。
通过网络协议栈的入包
网卡在收到一帧(所有校验和正常检查)时,网卡就会使用 DMA 来转发数据包到对于的内存区域。这意味着数据包是由驱动做了映射后直接从网卡队列拷贝到主内存区。当环形接受队列有数据进入的时候,网卡会产生一个硬中断,并且 CPU 会把处理事件下发到中断向量表中,执行驱动代码。
因为驱动的执行路径必须非常短快,具体数据处理可以延迟到驱动中断上下文之外,使用软中断来触发处理(NET_RX_SOFTIRQ)。在中断处理的时候中断请求是被屏蔽的,内核更愿意把这种长时间处理的任务放在中断上下文之外,以避免在中断处理的时候丢失中断事件。设备驱动开始使用 NAPI 循环和一个 CPU 一个内核线程(ksoftirqd)来从环形缓冲区中消费数据包。NAPI 循环的责任主要就是触发软中断(NET_RX_SOFTIRQ),由软中断处理程序处理数据包并且发送数据到网络协议栈。
设备驱动申请一个新的 socket 缓冲区(sk_buff)来存放入流量包。socket 缓冲区是内核中对数据包缓冲/处理抽象出来的一个最基础的数据结构。在整个网络协议栈中的上层中都在使用。
socket 缓冲区的结构体由多个字段,来标识不同的网络层。
从 CPU 队列上消费缓冲数据后,内核会填充这些元数据,复制 sk_buff 并且把它推到上游的网络层的自队列中做进一步处理。这是 IP 协议层在堆栈中注册的位置。IP 层执行一些基本的完整型检测,并且把包发送给 netfilter 的钩子函数。如果包没有被 netfilter 丢弃,IP 层会检测高级协议,并且为之前提取的协议把处理交给响应的处理函数。
数据最终被拷贝到 socket 关联的用户空间缓冲区。进程通过阻塞系统调用(recv、read)函数或通过某种轮询机制(epoll)主动接收数据。
在网卡把数据包拷贝到接受队列之后就触发了 XDP 的钩子函数,在这一点上我们可以高效的阻止申请各种各样的元数据结构,包括 sk_buffer。
如果我们看一下非常简单的可能使用场景,比如在高流量网络中的包过滤或者阻止 DDos 攻击,传统的网络防火墙方案(iptables)由于网络堆栈中的每个阶段都会引入大量的工作负载,这将不可避免地给机器造成压力。
在裸机速度下的 eBPF 和 XDP 包处理流程
在网络协议栈中的 XDP 的钩子

具体上来看在软中断任务中调度顺序执行的 iptables 规则,会在 IP 协议层中去匹配指定的 IP 地址,以决定是否丢弃这个数据包。和 iptables 不一样的是 XDP 会直接操作一个从 DMA 后端环形缓冲区中拿的原始的以太帧包,所以丢弃逻辑可以很早的执行,这样就节省了内核时间,避免了会导致协议栈执行导致的延时。
XDP 组成
正如你已经知道的,eBPF 的字节码可以挂载在各种策略执行点上,比如内核函数,socket,tracepoint,cgroup 层级或者用户空间符号。这样的话,每个 eBPF 程序操作特定的上下文- kprobes 场景下的 CPU 寄存器状态,socket 程序的 socket 缓冲区等等。用 XDP 的说法,生成的 eBPF 字节码的主干是围绕 XDP 元数据上下文建模的(xdp_md)。XDP 上下文包含了所有需要在原始形式下访问数据包的信息。
为了更好地理解 XDP 程序的关键模块,让我们剖析以下章节:
#include <linux/bpf.h>/** Comments from Linux Kernel:* Helper macro to place programs, maps, license in* different sections in elf_bpf file. Section names* are interpreted by elf_bpf loader.* End of comments* You can either use the helper header file below* so that you don't need to define it yourself:* #include <bpf/bpf_helpers.h> */
#define SEC(NAME) __attribute__((section(NAME), used))SEC("xdp")
int xdp_drop_the_world(struct xdp_md *ctx) {// drop everything// 意思是无论什么网络数据包,都drop丢弃掉return XDP_DROP;
}char _license[] SEC("license") = "GPL";
这个小 XDP 程序一旦加载到网卡上就会丢弃所有数据包。
-
第一部分是第一行的头文件
linux/bpf.h,它包含了BPF程序使用到的所有结构和常量的定义(除了一些特定的子系统,如TC,它需要额外的头文件)。理论上来说,所有的eBPF程序第一行都是这个头文件。 -
第二部分是第二行的宏定义,它的作用是赋予了SEC(NAME)这一串字符具有意义,即可以被编译通过。我截取了Linux内核代码里的注释,可以看出这段宏定义是为了ELF格式添加Section信息的。ELF全称是Executable and Linkable Format,就是可执行文件的一种主流格式(详细介绍点这里),广泛用于Linux系统,我们的BPF程序一旦通过编译后,也会是这种格式。下面代码中的SEC(“xdp”)和SEC(“license”)都是基于这个宏定义。
-
第三部分,也就是我们的代码主体,它是一个命名为xdp_drop_the_world函数,,返回值为int类型,接受一个参数,类型为xdp_md结构,上文已经介绍过,这个例子没有使用到这个参数。函数内的就是一行返回语句,使用XDP_DROP,也就是1,意思就是丢弃所有收到的数据包。
-
第四部分是最后一行的许可证声明。这行其实是给程序加载到内核时BPF验证器看的,因为有些eBPF函数只能被具有GPL兼容许可证的程序调用。因此,验证器会检查程序所使用的函数的许可证和程序的许可证是否兼容,如果不兼容,则拒绝该程序。
还有一点,大家是否注意到整个程序是没有main入口的,事实上,程序的执行入口可以由前面提到的ELF格式的对象文件中的Section来指定。入口也有默认值,它是ELF格式文件中.text这个标识的内容,程序编译时会将能看到的函数放到.text里面。
现在来看我们 XDP 程序中处理数据包逻辑最相关的部分。XDP 做了预定义的一组判定可以决定内核处理数据包流。
例如,我们可以让数据包通过,从而发送到常规的网络协议栈中,或者丢弃它,或者重定向数据包到其它的网卡等。
在我们的例子中,XDP_DROP 是说超快速的丢弃数据包。同时注意,我们声明了是在 prog 段中加载执行,eBPF 加载会检测加载(如果段名称没有找到会加载失败,但是我们可以根据 IP 来使用非标准段名称 )。下面我们来编译试运行一下上面的代码。
$ clang -Wall -target bpf -c xdp-drop.c -o xdp-drop.o
我们可以使用不同的用户空间工具把二进制目标代码加载到内核中(iproute2 的部分工具就可以),tc 或者 ip 是是常用的。XDP支持虚拟网卡,所以要直接看出上面程序的作用,我们可以把代码加载到一个已经存在的容器网卡上。我们会启动一个 nginx 容器,并且在加载 XDP 程序之前和之后分别启动一组 curl 请求。之前的 curl 请求会返回一个成功的 HTTP 状态码:
$ curl --write-out '%{http_code}' -s --output /dev/null 172.17.0.4:80
200
加载 XDP 字节码可以使用下面的命令:
$ sudo ip link set dev veth74062a2 xdp obj xdp-drop.o
我们会看到虚拟网卡上有 xdp 被激活的标识:
veth74062a2@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdp/id:37 qdisc noqueue master docker0 state UP group default
link/ether 0a:5e:36:21:9e:63 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::85e:36ff:fe21:9e63/64 scope link
valid_lft forever preferred_lft forever
curl 请求将会被阻塞一段时间直到返回如下的错误信息,这就说明 XDP 代码生效了,也是我们预期的效果:
curl: (7) Failed to connect to 172.17.0.4 port 80: No route to host
我们在测试完整之后,可以使用下面的命令卸载 XDP 程序:
$ sudo ip link set dev veth74062a2 xdp off
使用 GO 编写 XDP 程序
明确流程
明确整个流程的结构。用户态用Go程序来管理黑名单,比如动态添加或删除IP,然后将这些IP信息传递给内核中的eBPF程序。内核的XDP程序根据这些IP地址决定是否丢弃包。
那具体步骤:
-
eBPF程序(C语言),其中包含一个映射(map),用于存储黑名单IP。当接收到数据包时,检查源或目的IP是否在映射中,如果在,则丢弃包。
-
使用Go的ebpf库将eBPF程序加载到内核,并管理这个映射,比如添加或删除IP。
-
Go程序负责读取用户输入的黑名单IP,更新到映射中。
那现在需要分两部分:eBPF程序的C代码,和Go的用户态代码。
上面的代码片段演示了一些基本的概念,但是为了充分利用 XDP 的强大功能,我们将使用 Go 语言来制作稍微复杂点的软件 - 围绕某种规范用例构建的小工具:针对一些指定的黑名单 IP 地址进行包丢弃。
完整的代码以及如何构建这个工具的文档说明在这里。我们使用 gobpf 包,它提供了和 eBPF VM 交互的支持(加载程序到内核,访问/操作 eBPF map 以及其它功能)。大量的 eBPF 程序都可以直接由 C 编写,并且编译为 ELF 目标文件。但是可惜的是,基于 ELF 的 XDP 程序还不行。另外一种方法就是,通过 BCC 模块加载 XDP 程序仍然是可以的,但要是要依赖 libbcc。
不管怎么处理,BCC maps 有一个非常重要的限制:不能把他们挂到 bpffs 上面(事实上,你可以从用户空间挂 maps,但是启动 BCC 模块的是,它就很容易忽略任何的挂载对象)。我们的工具需要侵入黑名单的 map,同时需要在 XDP 程序加载到网卡上之后仍然可以有能力从 map 中添加或者删除元素。
我们就有足够的动力来考虑使用 ELF 目标文件支持 XDP 程序,所以我们给上游仓库提了这方面的 pr,并期望能合进去(目前这个 pr 已经被合并到 gobpf了)。我们认为这个功能对 XDP 程序的可移植性非常有价值,就像内核探测可以跨机器分布一样,即使它们不附带 clang、LLVM 和其他依赖项。
在网络(XDP)应用程序场景中,使用 Go 编写的用户空间控制程序。
选择eBPF库
在大多数情况下,eBPF 库主要协助实现两个功能:
将 eBPF 程序和 Map 载入内核并执行重定位,通过其文件描述符将 eBPF 程序与正确的 Map 进行关联。
与 eBPF Map 交互,允许对存储在 Map 中的键/值对进行标准的 CRUD 操作。
部分库也可以帮助你将 eBPF 程序附加到一个特定的钩子,尽管对于网络场景下,这可能很容易采用现有的 netlink API 库完成。
当涉及到 eBPF 库的选择时,我并不是唯一感到困惑的人(见[1], [2])。事实是每个库都有各自的范围和限制。
- Calico 在用 bpftool 和 iproute2 实现的 CLI 命令基础上实现了一个 Go 包装器。
- Aqua 实现了对 libbpf C 库的 Go 包装器。
- Dropbox 支持一小部分程序,但有一个非常干净和方便的用户API。
- IO Visor 的 gobpf 是 BCC 框架的 Go 语言绑定,它更注重于跟踪和性能分析。
- Cilium 和 Cloudflare 维护一个 纯 Go 语言编写的库 (以下简称 “libbpf-go”),它将所有 eBPF 系统调用抽象在一个本地 Go 接口后面。

编写eBPF代码
不用多说了,让我们从下面 XDP 代码开始浏览最重要的代码片段:
SEC("xdp/xdp_ip_filter")
int xdp_ip_filter(struct xdp_md *ctx) {void *end = (void *)(long)ctx->data_end;void *data = (void *)(long)ctx->data;u32 ip_src;u64 offset;u16 eth_type;struct ethhdr *eth = data;offset = sizeof(*eth);if (data + offset > end) {return XDP_ABORTED;}eth_type = eth->h_proto;/* handle VLAN tagged packet 处理 VLAN 标记的数据包*/if (eth_type == htons(ETH_P_8021Q) || eth_type ==
htons(ETH_P_8021AD)) {struct vlan_hdr *vlan_hdr;vlan_hdr = (void *)eth + offset;offset += sizeof(*vlan_hdr);if ((void *)eth + offset > end)return false;eth_type = vlan_hdr->h_vlan_encapsulated_proto;}/* let's only handle IPv4 addresses 只处理 IPv4 地址*/if (eth_type == ntohs(ETH_P_IPV6)) {return XDP_PASS;}struct iphdr *iph = data + offset;offset += sizeof(struct iphdr);/* make sure the bytes you want to read are within the packet's range before reading them * 在读取之前,确保你要读取的子节在数据包的长度范围内*/if (iph + 1 > end) {return XDP_ABORTED;}ip_src = iph->saddr;if (bpf_map_lookup_elem(&blacklist, &ip_src)) {return XDP_DROP;}return XDP_PASS;
}
代码看起来是稍微有点多,但是可以先忽略代码中负责处理 VLAN 标签的数据包的代码。我们先从 XDP 元信息中访问包数据开始,并且把这个指针转换成 ethddr 的内核结构。你同时会注意到检测包边界的几个条件。如果你忽略了他们,检查器会拒绝加载 XDP 子节代码。这个强制规则保证了 XDP 代码在内核中的的正常运行,避免有无效指针或者违反安全策略的代码被加载到内核。剩下的代码从 IP 协议头中提取了源 IP 地址,并且检测是否在黑名单 map 中。如果从 map 中查找到了,就会丢弃这个包。
Hook 结构体是负责在网络协议栈中加载或者卸载 XDP 程序。它实例化并且从对象文件中加载 XDP 模块,最终调用 AttachXDP 或者 RemoveXDP 方法。
IP 地址黑名单是通过标准的 eBPF maps 来管理的。我们调用 UpdateElement 和 DeleteElement 来分别注册或者删除 IP 信息。黑名单管理者也包含了获取 map 中可用的 IP 地址列表的方法。
其它的代码把所有的代码片段组合起来,以提供良好的 CLI 体验,用户可以利用这种体验执行 XDP 程序附加/删除和操作 IP 黑名单。要了解更多细节,请看源码。
保存为 xdp_prog.c
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>struct {__uint(type, BPF_MAP_TYPE_HASH);__type(key, __u32);__type(value, __u8);__uint(max_entries, 1024);
} blacklist SEC(".maps");SEC("xdp")
int xdp_filter(struct xdp_md *ctx) {void *data_end = (void *)(long)ctx->data_end;void *data = (void *)(long)ctx->data;struct ethhdr *eth = data;if (eth + 1 > data_end)return XDP_PASS;if (eth->h_proto != htons(ETH_P_IP))return XDP_PASS;struct iphdr *ip = (struct iphdr *)(eth + 1);if (ip + 1 > data_end)return XDP_PASS;__u32 ip_src= ip->saddr;__u8 *val = bpf_map_lookup_elem(&blacklist, &ip_src);if (val)return XDP_DROP;return XDP_PASS;
}char _license[] SEC("license") = "GPL";
clang -O2 -Wall -target bpf -c xdp_prog.c -o xdp_prog.o
编写Go代码
package mainimport ("encoding/binary""log""net""os""os/signal""syscall""github.com/cilium/ebpf""github.com/cilium/ebpf/link""github.com/cilium/ebpf/rlimit"
)func main() {// 解除内存锁定限制if err := rlimit.RemoveMemlock(); err != nil {log.Fatal(err)}// 加载eBPF ELF文件coll, err := ebpf.LoadCollectionSpec("xdp_prog.o")if err != nil {log.Fatalf("加载eBPF集合失败: %v", err)}// 实例化eBPF对象var objs struct {XdpProg *ebpf.Program `ebpf:"xdp_filter"`Blacklist *ebpf.Map `ebpf:"blacklist"`}if err := coll.LoadAndAssign(&objs, nil); err != nil {log.Fatalf("加载eBPF对象失败: %v", err)}defer objs.XdpProg.Close()defer objs.Blacklist.Close()// 获取网络接口iface, err := net.InterfaceByName("eth0") // 修改为你的接口名if err != nil {log.Fatalf("获取网络接口失败: %v", err)}// 附加XDP程序l, err := link.AttachXDP(link.XDPOptions{Program: objs.XdpProg,Interface: iface.Index,Flags: link.XDPGenericMode,})if err != nil {log.Fatal(err)}defer l.Close()log.Printf("XDP程序已附加到 %q (索引 %d)", iface.Name, iface.Index)// 初始化黑名单blacklist := []string{"192.168.1.100","10.0.0.5",// 添加更多IP...}for _, ipStr := range blacklist {ip := net.ParseIP(ipStr).To4()if ip == nil {log.Printf("无效IPv4地址: %s", ipStr)continue}ipU32 := binary.BigEndian.Uint32(ip)if err := objs.Blacklist.Put(ipU32, uint8(1)); err != nil {log.Printf("添加IP %s 失败: %v", ipStr, err)} else {log.Printf("已屏蔽IP: %s", ipStr)}}// 等待终止信号sigCh := make(chan os.Signal, 1)signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)<-sigChlog.Println("卸载程序...")
}
sudo go run main.go
动态更新黑名单
// 示例:运行时添加新IP
func addToBlacklist(ipStr string, blacklist *ebpf.Map) error {ip := net.ParseIP(ipStr).To4()if ip == nil {return fmt.Errorf("invalid IPv4 address")}ipU32 := binary.BigEndian.Uint32(ip)return blacklist.Put(ipU32, uint8(1))
}
相关文章:
高速网络包处理,基础网络协议上内核态直接处理数据包,XDP技术的原理
文章目录 预备知识TCP/IP 网络模型(4层、7层)iptables/netfilterlinux网络为什么慢 DPDKXDPBFPeBPFXDPXDP 程序典型执行流通过网络协议栈的入包XDP 组成 使用 GO 编写 XDP 程序明确流程选择eBPF库编写eBPF代码编写Go代码动态更新黑名单 预备知识 TCP/IP…...
C++:背包问题习题
1. 货币系统 1371. 货币系统 - AcWing题库 给定 V 种货币(单位:元),每种货币使用的次数不限。 不同种类的货币,面值可能是相同的。 现在,要你用这 V 种货币凑出 N 元钱,请问共有多少种不同的…...
数据可信安全流通实战,隐语开源社区Meetup武汉站开放报名
隐语开源社区 Meetup 系列再出发!2025 年将以武汉为始发站,聚焦"技术赋能场景驱动",希望将先进技术深度融入数据要素流转的各个环节,推动其在实际应用场景中落地生根,助力释放数据要素的最大潜能!…...
java使用Apache POI 操作word文档
项目背景: 当我们对一些word文档(该文档包含很多的标题比如 1.1 ,1.2 , 1.2.1.1, 1.2.2.3)当我们删除其中一项或者几项时,需要手动的对后续的进行补充。该功能主要是对标题进行自动的补充。 具…...
【 C/C++ 包管理工具】vcpkg安装+使用
【 C/C 包管理工具】vcpkg安装使用 Vcpkg 是由 Microsoft 和 C 社区维护的免费开源 C/C 包管理器,可在 Windows、macOS 和 Linux 上运行。 可以很方便的安装管理 C/C 库。 1. 安装 不要安装到Program Files这种有空格的路径下,否则后面安装库可能出现…...
免费开源的NAS解决方案:TrueNAS
TrueNAS是业内知名的FreeNAS系统的升级版,是一款开源的网络存储系统,具有高性能、稳定性和易用性等优点。 TrueNAS目前有三个版本,分别是TrueNAS CORE、TrueNAS ENTERPRISE、TrueNAS SCALE。其中,TrueNAS CORE基于FreeBSD开发&…...
LeetCode热题100精讲——Top1:两数之和【哈希】
你好,我是安然无虞。 文章目录 题目背景两数之和C解法Python解法 题目背景 如果大家对于 哈希 类型的概念并不熟悉, 可以先看我之前为此专门写的算法详解: 蓝桥杯算法竞赛系列第九章巧解哈希题,用这3种数据类型足矣 两数之和 题目链接:两数…...
github上传操作简单说明
前期准备 0.下载git(如果已经有了就不用了) 1.在GitHub上新建一个存储库 2.先在本地创建一个目录作为本地库目录,在目录里打开git bash进行上传 上传过程 echo "# Garbled_repair" >> README.md 作用:创建一个…...
GitLens with `Commit Graph`
文章目录 GitLens with Commit Graph GitLens with Commit Graph 想要更直观地查看 Git 提交历史?我打包了一个支持 Commit Graph 的 GitLens 版本,让你轻松在 VSCode 中查看分支、合并、变更记录等内容,一目了然! 📌…...
Rocky9.5基于sealos快速部署k8s集群
首先需要下载 Sealos 命令行工具,sealos 是一个简单的 Golang 二进制文件,可以安装在大多数 Linux 操作系统中。 以下是一些基本的安装要求: 每个集群节点应该有不同的主机名。主机名不要带下划线。 所有节点的时间需要同步。 需要在 K8s …...
阿里云服务器环境部署 四 MySQL主从配置
安装MySQL 导入mysql镜像 docker load -i /opt/dockerinstall/mysql/mysql-8.1.0.tar docker run --privilegedtrue --name mysql8 --restartunless-stopped -e MYSQL_ROOT_PASSWORD123456 -p 3306:3306 -v /usr/local/mysql/logs:/var/log/mysql -v /usr/local/mysql/d…...
GPT-5 将免费向所有用户开放?
GPT-5 将免费向所有用户开放? 硅谷知名分析师 Ben Thompson 最近与 OpenAI CEO Sam Altman 进行了一场深度对谈,其中Sam Altman透漏GPT-5将免费向大家发放。 OpenAI 这波操作可不是一时冲动,而是被逼出来的。DeepSeek 这个新秀横空出世&am…...
web客户端存储,IndexDB相关讲解
IndexDB详细讲解 IndexedDB 是浏览器提供的一种底层 API,用于在客户端存储大量结构化数据。相比 Web Storage(localStorage/sessionStorage),它支持更复杂的数据结构、事务处理、索引查询等高级功能。以下是一个系统化的讲解: 一、核心概念 1、数据库(Database) 每…...
excel文件有两列,循环读取文件两列赋值到字典列表。字典的有两个key,分别为question和answer。将最终结果输出到json文件
import pandas as pd import json# 1. 读取 Excel 文件(假设列名为 question 和 answer) try:df pd.read_excel("input.xlsx", usecols["question", "answer"]) # 明确指定列 except Exception as e:print(f"读取文…...
项目日记 -云备份 -服务器配置信息模块
博客主页:【夜泉_ly】 本文专栏:【项目日记-云备份】 欢迎点赞👍收藏⭐关注❤️ 代码已上传 gitee 目录 前言配置信息文件文件配置类getInstance 获得实例readConfigFile 读取配置信息文件 测试 #mermaid-svg-ewlCpjdOf0q0VTLI {font-family:…...
gralloc usage flags
下面这些示例主要说明了 gralloc usage flags 在图像处理和多媒体应用中如何影响性能和正确性。让我们逐个详细分析每个问题的 根因 和 修复方案,并深入解析 gralloc 标志对 缓存管理 和 数据流 的影响。 ✅ Example 1: 长曝光快照耗时异常 📌 问题描述…...
Mysql配套测试之查询篇
🏝️专栏:Mysql_猫咪-9527的博客-CSDN博客 🌅主页:猫咪-9527-CSDN博客 “欲穷千里目,更上一层楼。会当凌绝顶,一览众山小。” 目录 条件查询简单测试: 1.查询英语成绩不及格的同学(<60) 2…...
mysql——第二课
学生表 CREATE TABLE student (id int(11) NOT NULL AUTO_INCREMENT,name varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,sex varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,age int(11) DEFAULT NULL,c_id int(10) DEFAULT NULL,PRIMARY KEY (id),KEY c_id (c_id),CONSTR…...
Python网络编程入门
一.Socket 简称套接字,是进程之间通信的一个工具,好比现实生活中的插座,所有的家用电器要想工作都是基于插座进行,进程之间要想进行网络通信需要Socket,Socket好比数据的搬运工~ 2个进程之间通过Socket进行相互通讯&a…...
arm linux下的读写信号量rw_semphore的实现
本文基于arm linux 5.10来介绍内核中使用的读写信号量rw remphore的实现代码。 内核中信号量结构体struct rw_semaphore的定义在include/linux/rwsem.h 32位architectures下,结构体struct rw_semaphore中的count的使用如下: 先来看信号量的定义和初始化…...
完整的类在JVM中的生命周期详解
首先给出一个示例代码: 示例的目标是展示一个多功能的类结构,包含继承、接口实现、静态成员、本地方法、线程安全等特性,同时模拟一个简单的“计算器”场景,计算并管理数字。(尽量将所有的 Java 组件和关键字都给出&am…...
Flutter中常用命令
1.检测flutter运行环境 flutter doctor 2.升级flutter flutter upgrade 3.查看flutter 版本 flutter --version 4.查看连接的设备 flutter devices 5.运行flutter项目 flutter run 或者在vscode中按FnF5 6.打包 flutter build apk //默认打release包 7.开…...
C#里使用libxl的数字格式
由于EXCEL里可以表示不同的数字格式, 比如表示货币数字时,与表示普通序号的数字就不一样。 还有科学计算表示的数字使用小数点位数与普通货币也不一样。 如下所示: 要使用这些格式, 下面创建一个例子来演示保存这些数字格式: private void button11_Click(object send…...
c#难点整理2
1.对象池的使用 就是先定义一系列的对象,用一个,调一个。 public class ObjectPool<T> where T : new(){private Queue<T> pool; // 用于存储对象的队列private int maxSize; // 对象池的最大容量// 构造函数public ObjectPool(int maxSi…...
android adjust 卸载与重装监测
想要洞察应用内用户的留存率,可以通过Adjust 的卸载与重装进行监测 名词解释: 卸载:集成完成后,卸载应用,安装状态为:卸载 重装:如果应用已经卸载,但一段时间后又进行安装,则会被视为重装。 📢📢📢:adjust 文件中说到24 小时后,可以再 adjust 控制台看安装…...
自然语言处理(5)—— 中文分词
中文分词的基本原理及实现 1. 什么是词2. 基本原理3. 发展趋势:多数场景无需显式分词 信息处理的目标是使用计算机能够理解和产生自然语言。而自然语言理解和产生的前提是对语言能够做出全面的解析。 汉语词汇是语言中能够独立运用的最小的语言单位,是语…...
解锁物联网高效开发,Synaptics SYN43756E Wi-Fi 6E 芯片登场
Synaptics 的 SYN43756E 芯片是一款高性能的 Wi-Fi 6E 支持 11a/b/g/n/ac/ax 的物联网(IoT)SoC,具备多项先进特性,适用于多种应用场景,以下是其主要优势: 1. 广泛的应用场景 智慧家庭:支持多种…...
C++和标准库速成(十二)——练习
目录 练习1.1题目答案 练习1.2题目答案 练习1.3题目答案 练习1.4题目答案 练习1.5题目答案 练习1.6题目答案 参考 练习1.1 题目 修改下面的Employee结构体,将其放在一个名为HR的名称空间中。你必须对main()中的代码进行那些修改才能使用此新实现?此外&a…...
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能📚页面效果📚指令输入�…...
5、linux c 线程 - 上
【四】线程 1. 线程的创建 #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg); pthread_t *thread:指向线程标识符的指针,用于存储新创建线程的 ID。 const p…...
