当前位置: 首页 > news >正文

高速网络包处理,基础网络协议上内核态直接处理数据包,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程序依次执行:

  1. 提取包头中的信息(例如 IP、MAC、Port、Proto 等),

执行到程序时,系统会传递给它一个上下文对象(context object)作为参赛 (即 struct xdp_md *ctx,后面有例子),其中包括了指向原 始包数据的指针,以及描述这个包是从哪个网卡的哪个接口接收上来的等元数据字段。

  1. 读取或更新一些资源的元信息(例如更新统计信息);

解析包数据之后,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 程序一旦加载到网卡上就会丢弃所有数据包。

  1. 第一部分是第一行的头文件linux/bpf.h,它包含了BPF程序使用到的所有结构和常量的定义(除了一些特定的子系统,如TC,它需要额外的头文件)。理论上来说,所有的eBPF程序第一行都是这个头文件。

  2. 第二部分是第二行的宏定义,它的作用是赋予了SEC(NAME)这一串字符具有意义,即可以被编译通过。我截取了Linux内核代码里的注释,可以看出这段宏定义是为了ELF格式添加Section信息的。ELF全称是Executable and Linkable Format,就是可执行文件的一种主流格式(详细介绍点这里),广泛用于Linux系统,我们的BPF程序一旦通过编译后,也会是这种格式。下面代码中的SEC(“xdp”)和SEC(“license”)都是基于这个宏定义。

  3. 第三部分,也就是我们的代码主体,它是一个命名为xdp_drop_the_world函数,,返回值为int类型,接受一个参数,类型为xdp_md结构,上文已经介绍过,这个例子没有使用到这个参数。函数内的就是一行返回语句,使用XDP_DROP,也就是1,意思就是丢弃所有收到的数据包。

  4. 第四部分是最后一行的许可证声明。这行其实是给程序加载到内核时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地址决定是否丢弃包。

那具体步骤:

  1. eBPF程序(C语言),其中包含一个映射(map),用于存储黑名单IP。当接收到数据包时,检查源或目的IP是否在映射中,如果在,则丢弃包。

  2. 使用Go的ebpf库将eBPF程序加载到内核,并管理这个映射,比如添加或删除IP。

  3. 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 网络模型&#xff08;4层、7层&#xff09;iptables/netfilterlinux网络为什么慢 DPDKXDPBFPeBPFXDPXDP 程序典型执行流通过网络协议栈的入包XDP 组成 使用 GO 编写 XDP 程序明确流程选择eBPF库编写eBPF代码编写Go代码动态更新黑名单 预备知识 TCP/IP…...

C++智能指针详解

C智能指针详解 目录 智能指针概述为什么需要智能指针C标准库中的智能指针 std::unique_ptrstd::shared_ptrstd::weak_ptr 智能指针的实际应用智能指针的最佳实践总结 智能指针概述 智能指针是C中用于自动管理动态分配内存的对象&#xff0c;它们遵循RAII&#xff08;资源获…...

git 设置保存密码 git保存密码

目录 长久保存密码 长久保存密码 git push和git pull都能使用。 git config --global credential.helper store 然后执行一次 git pull&#xff0c;Git 会提示输入用户名和密码&#xff0c; 输入后保存路径&#xff1a; ~/.git-credentials &#xff0c; Windows系统&…...

clamav服务器杀毒(Linux服务器断网状态下如何进行clamav安装、查杀)

ClamAV服务器杀毒&#xff08;服务器断网状态也可以使用该方法&#xff09; 服务器因为挖矿病毒入侵导致断网&#xff0c;进行离线的clamav安装并查杀 安装包下载网址&#xff1a;https://www.clamav.net/downloads 安装.deb&#xff0c;如果服务器处于断网状态&#xff0c;可以…...

深入剖析 RocketMQ 的 ConsumerOffsetManager

在消息队列系统中&#xff0c;准确记录和管理消息的消费进度是保障系统可靠性和数据一致性的关键。RocketMQ 作为一款高性能、高可用的分布式消息队列&#xff0c;其 ConsumerOffsetManager 组件在消费进度管理方面发挥着至关重要的作用。本文将详细介绍 ConsumerOffsetManager…...

一文了解 threejs 中.bin 文件与 .gltf 文件 和 .glb 文件三者之间的关系

文章目录 前言一、.bin 文件的作用二、.gltf 文件的作用三、.gltf 和 .bin 的关系四、.glb 文件的作用五、如何查看或编辑 .bin 文件六、总结 前言 在 GLTF 生态中&#xff0c;.bin 文件是 GLTF 格式的重要组成部分&#xff0c;通常与 .gltf 文件一起使用。以下是它们的详细关…...

蓝桥杯(N皇后问题)------回溯法

题目描述 在 NN 的方格棋盘放置了 N 个皇后&#xff0c;使得它们不相互攻击&#xff08;即任意 2 个皇后不允许处在同一排&#xff0c;同一列&#xff0c;也不允许处在与棋盘边框成 45 角的斜线上。你的任务是&#xff0c;对于给定的 N&#xff0c;求出有多少种合法的放置方法…...

Linux系统中关闭Docker服务并禁止其开机自启 、docker 安装目录结构分析 | 【du -sh *】

文章目录 在Linux系统中关闭Docker服务并禁止其开机自启&#xff0c;具体步骤如下&#xff1a;适用于使用 systemd 的系统&#xff08;如Ubuntu 16.04/CentOS 7等&#xff09;适用于使用 SysVinit 或 Upstart 的旧版系统其他注意事项示例输出 linux 下 一条命令实现 统计各个目…...

免费下载 | 2025低空经济产业发展报告

低空经济概览 产业链条&#xff1a;低空经济产业链分为上游的低空经济基础设施&#xff08;如空管系统、飞行基地等&#xff09;、中游的低空制造&#xff08;包括无人机、eVTOL、直升机等飞行器的设计、研发、生产&#xff09;和下游的低空运营及飞行服务。低空经济以低空空域…...

单一职责原则开闭原则其他开发原则

一、单一职责原则&#xff08;Single Responsibility Principle, SRP&#xff09; 定义 一个类应该有且仅有一个引起它变化的原因&#xff08;即一个类只负责一个职责&#xff09;。 核心思想 高内聚&#xff1a;类的功能高度集中 低耦合&#xff1a;减少不同职责之间的相互影…...

(自用)yolo算法学习

1.难受中&#xff0c;看了教程过后无从下手啊 2.pycharm专业版成功就好 3.安装包时出先问题 (base) PS G:\pycharm\projects\yolo\yolov5> pip install opencv-python>4.1.1 Requirement already satisfied: opencv-python>4.1.1 in g:\anaconda\app\lib\site-packa…...

手机号登录与高并发思考

基础逻辑 一般来说这个验证码登录分为手机号、以及邮箱登录 手机号短信验证&#xff0c;以腾讯云SMS 服务为例&#xff1a; 这个操作无非对后端来说就是两个接口&#xff1a; 一个是获取验证码&#xff0c;这块后端生成6位数字expire_time 去推送到腾讯云sdk &#xff0c;腾…...

Linux系统管理与编程07:任务驱动综合应用

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 [环境] windows11、centos9.9.2207、zabbix6、MobaXterm、Internet环境 [要求] zabbix6.0安装环境&#xff1a;Lamp&#xff08;linux httpd mysql8.0 php&#xff09; [步骤] 2 …...

12:表的内外连接

1. 内连接 内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选&#xff0c;前面学习的查询都是内连接&#xff0c;也是在开发过程中使用的最多的连接查询。 新的内连接的语法&#xff1a; select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件; //通过…...

Mybatis的基础操作——03

写mybatis代码的方法有两种&#xff1a; 注解xml方式 本篇就介绍XML的方式 使用XML来配置映射语句能够实现复杂的SQL功能&#xff0c;也就是将sql语句写到XML配置文件中。 目录 一、配置XML文件的路径&#xff0c;在resources/mapper 的目录下 二、写持久层代码 1.添加mappe…...

在CentOS系统上运行Ruby on Rails应用的详细步骤

以下是AI生成&#xff0c;仅做备份。 1. 安装必要的依赖 在CentOS上&#xff0c;首先要安装一些基础的开发工具和依赖库&#xff0c;它们能确保后续的安装和运行过程顺利进行。 sudo yum update -y sudo yum install -y git gcc-c patch readline readline-devel zlib zlib-…...

《AI大模型趣味实战》第6集:基于大模型和RSS聚合打造个人新闻电台

《AI大模型趣味实战》第6集&#xff1a;基于大模型和RSS聚合打造个人新闻电台 摘要 本文将带您探索如何结合AI大模型和RSS聚合技术&#xff0c;打造一个功能丰富的个人新闻电台系统。我们将使用Python和PyQt5构建一个桌面应用程序&#xff0c;该应用可以从多个RSS源抓取新闻&…...

Unity 与 JavaScript 的通信交互:实现跨平台的双向通信

前言 在现代游戏开发和 Web 应用中&#xff0c;Unity 和 JavaScript 的结合越来越常见。Unity 是一个强大的跨平台游戏引擎&#xff0c;而 JavaScript 是 Web 开发的核心技术之一。通过 Unity 和 JavaScript 的通信交互&#xff0c;开发者可以实现从 Unity 到 Web 页面的功能扩…...

Sql Server 索引性能优化 分析以及分表

定位需优化语句 根据工具 skywking 或者开启慢查询日志 找到 慢sql 的语句根据 执行过程 来 判断 慢的原因 row filter 指标 看查了多少数据 比例多少 type 看下是单表 还是 join联表 比如 执行步骤多 没索引 优化方向 减少执行次数索引 没索引考虑加索引 加索引 尽量选择 i…...

Vue.js 模板语法全解析:从基础到实战应用

引言 在 Vue.js 的开发体系中&#xff0c;模板语法是构建用户界面的核心要素&#xff0c;它让开发者能够高效地将数据与 DOM 进行绑定&#xff0c;实现动态交互效果。通过对《Vue.js 快速入门实战》中关于 Vue 项目部署章节&#xff08;实际围绕 Vue 模板语法展开&#xff09;…...

【JVM】内存区域划分,类加载机制和垃圾回收机制

本篇内容为了解 JVM 的内存区域划分&#xff0c;类加载机制&#xff0c;垃圾回收机制。实际开发中几乎用不到&#xff0c;但为了某些情况我们又不得不了解。 目录 一、JVM中的内存区域划分 1.1 内存区域划分考点 二、JVM的类加载机制 2.1 类加载流程 2.2 类加载什么时候会…...

代码随想录算法训练营第十四天|替换数字

文档讲解&#xff1a;代码随想录 难度&#xff1a;easy 附&#xff1a;冲 passion&#xff01;&#xff01;&#xff01;passion&#xff01;&#xff01;&#xff01;passion&#xff01;&#xff01;&#xff01; 替换数字 卡码网题目链接(opens new window) 给定一个字符串…...

Java实体类(Javabean)-编程规范

Java学习笔记-Java实体类详解 今天我们要聊一个看似简单却至关重要的知识点——Java实体类。就像快递小哥打包物件需要包装盒一样&#xff0c;在Java世界里处理数据也需要专门的容器&#xff0c;这就是我们的实体类&#xff01; 一、实体类是什么&#xff1f;——程序的"…...

深入解析 Java Stream API:筛选子节点的优雅实现!!!

&#x1f680; 深入解析 Java Stream API&#xff1a;筛选子节点的优雅实现 &#x1f527; 大家好&#xff01;&#x1f44b; 今天我们来聊聊 Java 8 中一个非常常见的操作&#xff1a;使用 Stream API 从 Map 中筛选出特定条件的元素。&#x1f389; 具体来说&#xff0c;我们…...

Vala编程语言教程-面向对象编程语基础

基础 尽管Vala语言并不强制你使用对象进行编程&#xff0c;但有些功能只能通过对象的方式来实现。因此&#xff0c;在大多数情况下&#xff0c;你肯定会希望采用面向对象的编程风格。与大多数当前的编程语言一样&#xff0c;为了定义你自己的对象类型&#xff0c;你需要编写一个…...

写读后感的时候,可以适当地引用书中的内容吗?

写读后感时&#xff0c;适当地引用书中的内容是可以的&#xff0c;这样可以更好地支持你的观点和感受&#xff0c;增强文章的可信度和说服力。 引用书中的内容可以帮助读者更好地理解你所讨论的主题和人物&#xff0c;同时也可以展示你对原著的深入理解和阅读能力。但是&#…...

计算机网络高频(二)TCP/IP基础

计算机网络高频(二)TCP/IP基础 1.什么是TCP/IP⭐⭐ TCP/IP是一种网络通信协议,它是互联网中最常用的协议之一。TCP/IP有两个基本的协议:TCP(传输控制协议)和IP(互联网协议)。 TCP(Transmission Control Protocol,传输控制协议)是一种可靠的、面向连接的协议。它负…...

蓝桥杯 之 数论

文章目录 习题质数找素数 LCM报数游戏 快速幂数字诗意 组合数与错位排序小蓝与钥匙 同余取模 数论&#xff0c;就是一些数学问题&#xff0c;蓝桥杯十分喜欢考察&#xff0c;常见的数论的问题有&#xff1a;取模&#xff0c;同余&#xff0c;大整数分解&#xff0c;素数&#x…...

无法写入文件:(FileSystemError): Error: EPERM: operation not permitted, open...)

问题分析&#xff1a; 当我想在Visual Studio Code中编写文件时&#xff0c;出现无法写入文件的错误&#xff0c;发现是权限的问题 解决办法&#xff1a; 右键应用图标 → 以管理员身份运行就可以了...

Java爬虫抓取B站视频信息

依赖 <dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.17.2</version> <!-- 最新版可去官网查看 --></dependency>编码 public static List<VideoDto> parseSearchPage(Str…...

Sql Server数据迁移易错的地方

背景&#xff1a;之前一直台式机&#xff0c;毕业准备答辩了&#xff0c;要将代码搬到笔记本运行才方便些。这个Sql数据弄过来搞了好几个小时 还原备份报错&#xff1a;媒体簇的结构不正确。SQL Server 无法处理此媒体簇。 解决&#xff1a;升级到sql server版本比备份的那个高…...

七、服务器远程桌面报错

&#x1f33b;&#x1f33b;目录&#x1f33b;&#x1f33b; 一、远程桌面报错-用户账户限制&#xff08;例如&#xff0c;时间限制&#xff09;会阻止你登录。 一、远程桌面报错-用户账户限制&#xff08;例如&#xff0c;时间限制&#xff09;会阻止你登录。 原因是被远程的系…...

JAVA 之「优先队列」:大顶堆与小顶堆的实现与应用

Java 优先队列&#xff1a;大顶堆与小顶堆的实现与应用 文章目录 Java 优先队列&#xff1a;大顶堆与小顶堆的实现与应用一、什么是优先队列和堆&#xff1f;1. 优先队列2. 堆 二、Java PriorityQueue 基本用法1. 默认小顶堆示例代码输出 2. 实现大顶堆示例代码输出 三、大顶堆…...

压缩壳学习

壳是什么 壳就是软件的一个保护套&#xff0c;防止软件被进行反编译或被轻易地修改。 其作用就是为了保护软件。 常见的大类壳有压缩壳、加密壳、VM 壳的分类。 压缩壳顾名思义就是用来减小软件的文件大小的&#xff1b;加密壳&#xff0c;通过加密软件来保护软件&#xff…...

VRRP配置双出口ipsec隧道建立。

背景&#xff1a;在做毕设时&#xff0c;发现规划的不是那么合理&#xff0c;vrrp主备切换后&#xff0c;ipsec隧道并没有跟着切换到与备防火墙建立隧道&#xff0c;这是因为配置了双出口&#xff0c;路由的设计导致vrrp主备切换ipsec隧道没有跟着切换。 fw1为主&#xff0c;fw…...

机器学习——Numpy的神奇索引与布尔索引

在 NumPy 中&#xff0c;神奇索引&#xff08;Fancy Indexing&#xff09; 和 布尔索引&#xff08;Boolean Indexing&#xff09; 是两种强大的索引方式&#xff0c;用于从数组中提取特定元素或子集。以下是它们的详细说明和示例&#xff1a; 1. 神奇索引&#xff08;Fancy In…...

Linux:进程间通信

文章目录 前言一、进程间通信介绍1.1 进程间通信的目的1.2 进程间通信的发展与分类 二、管道2.1 匿名管道原理2.2 通信管道会出现的情况和特性&#xff08;重要&#xff09;2.3 命名管道2.3.1 命名管道与匿名管道的区别 三、system V3.1 共享内存原理3.2 键值3.2.1 键值生成原理…...

Mysql配套测试之查询篇

&#x1f3dd;️专栏&#xff1a;Mysql_猫咪-9527的博客-CSDN博客 &#x1f305;主页&#xff1a;猫咪-9527-CSDN博客 “欲穷千里目&#xff0c;更上一层楼。会当凌绝顶&#xff0c;一览众山小。” 目录 条件查询简单测试&#xff1a; 1.查询英语成绩不及格的同学(<60) 2…...

基于SSM框架的汽车租赁平台(源码+lw+部署文档+讲解),源码可白嫖!

摘要 时代在飞速进步&#xff0c;每个行业都在努力发展现在先进技术&#xff0c;通过这些先进的技术来提高自己的水平和优势&#xff0c;汽车租赁平台当然不能排除在外。汽车租赁平台是在实际应用和软件工程的开发原理之上&#xff0c;运用Java语言以及SSM框架进行开发&#x…...

常考计算机操作系统面试习题(三下)

20. 请求页式存储管理系统缺页率计算 题目&#xff1a; 假设一个作业的页面走向为 1、2、3、4、1、2、5、1、2、3、4、5&#xff0c;当分配给该作业的物理块数分别为 3 和 4 时&#xff0c;计算采用下述页面置换算法的缺页率&#xff1a; (1) 先进先出&#xff08;FIFO&…...

Spring IOC核心详解:掌握控制反转与依赖注入

文章目录 前言一、IOC核心思想二、IOC容器实现1.核心接口&#xff1a;2.XML配置范例 三、Bean管理实践1.创建对象&#xff08;1&#xff09;基于xml方式创建对象&#xff08;2&#xff09;用注解的方式创建对象 2.依赖注入&#xff08;1&#xff09;基于xml方式注入属性基础类型…...

Servlet、HttpServletRequest、HttpServletResponse、静态与动态网页、jsp、重定向与转发

DAY15.2 Java核心基础 JavaWeb 要想通过浏览器或者客户端来访问java程序&#xff0c;必须通过Servlet来处理 没有Servlet&#xff0c;java是无法处理web请求的 Web交互&#xff1a; 接收请求HttpServletRequest&#xff1a;可以获取到请求的信息&#xff0c;比如uri&#…...

Linux 内核源码阅读——ipv4

Linux 内核源码阅读——ipv4 综述 在 Linux 内核中&#xff0c;IPv4 协议的实现主要分布在 net/ipv4/ 目录下。以下是一些关键的源文件及其作用&#xff1a; 1. 协议栈核心 net/ipv4/ip_input.c&#xff1a;处理接收到的 IPv4 数据包&#xff08;输入路径&#xff09;。net…...

组合总和 II:去重逻辑深度解析

组合总和 II&#xff1a;去重逻辑深度解析 在算法中&#xff0c;解决“组合总和 II”这类问题时&#xff0c;去重往往是最具挑战性的一环。如何避免重复组合&#xff0c;同时保证所有组合的唯一性&#xff0c;是实现高效算法的关键。今天&#xff0c;我们就来深度解析组合总和…...

蓝桥杯备考:二分答案之路标设置

最大距离&#xff0c;找最小空旷指数值&#xff0c;我们是很容易想到用二分的&#xff0c;我们再看看这个答案有没有二段性 是有这么个二段性的&#xff0c;我们只要二分就行了&#xff0c;但是二分的check函数是有点不好想的&#xff0c;我们枚举空旷值的时候&#xff0c;为了…...

[HY000][1366] Incorrect string value: ‘张三‘ for column ‘name‘ at row 1

常见原因 字符集不兼容 插入的数据包含当前字符集&#xff08;如 latin1&#xff09;不支持的特殊字符&#xff08;如中文、Emoji 等&#xff09;。 表、列或连接的字符集未正确配置为支持目标字符&#xff08;如未使用 utf8mb4&#xff09;。 客户端/服务端编码不一致 客户…...

什么是C++对象之间的view proxies

在C中&#xff0c;view proxies 是一种轻量级的对象&#xff0c;用于提供对另一个对象的间接访问或视图&#xff0c;而不直接拥有或管理该对象的数据。它们通常用于简化对复杂数据结构的访问&#xff0c;或在不需要复制数据的情况下提供特定的视图。 1. View Proxies 的核心概…...

MyBatis参数赋值技巧:#{} 和 ${} 的区别与实践

目录 一、前言二、 #{} 和${} 的使用方法和区别2.1 #{}使用方法2.2 ${}使用方法2.3#{} 和 ${} 的主要区别2.4使用建议 三、总结 一、前言 在 MyBatis 中&#xff0c;#{} 和 ${} 都用于在 SQL 语句中绑定参数&#xff0c;但它们在具体实现和安全性方面有所不同。理解它们的区别…...

5-1 使用ECharts将MySQL数据库中的数据可视化

方法一&#xff1a;使用Python Flask框架搭建API 对于技术小白来说&#xff0c;使用ECharts将MySQL数据库中的数据可视化需要分步骤完成。以下是详细的实现流程&#xff1a; 一、技术架构‌ 后端服务‌&#xff1a;使用Python Flask框架搭建API&#xff08;简单易学&#xff…...

协程的调度的对称与非对称

下图表示的就是对称协程&#xff0c;进入到该协程之后只能有一个操作就是yield&#xff0c;把cpu让回给调度器; 下图表示非对称协议&#xff0c;可以有两个操作&#xff0c;就是resume和yield&#xff0c;从哪里resume的&#xff0c;yield就会回到该位子&#xff1b;...