从用户空间运行 BPF 程序¶
本文档描述了用于从用户空间运行 BPF 程序的 BPF_PROG_RUN
功能。
概述¶
可以通过 bpf()
系统调用使用 BPF_PROG_RUN
命令在内核中执行 BPF 程序,并将结果返回到用户空间。这可用于针对用户提供的上下文对象对 BPF 程序进行单元测试,并作为在内核中显式执行程序以产生副作用的方式。该命令之前名为 BPF_PROG_TEST_RUN
,并且这两个常量在 UAPI 头文件中继续定义,并别名为相同的值。
BPF_PROG_RUN
命令可用于执行以下类型的 BPF 程序
BPF_PROG_TYPE_SOCKET_FILTER
BPF_PROG_TYPE_SCHED_CLS
BPF_PROG_TYPE_SCHED_ACT
BPF_PROG_TYPE_XDP
BPF_PROG_TYPE_SK_LOOKUP
BPF_PROG_TYPE_CGROUP_SKB
BPF_PROG_TYPE_LWT_IN
BPF_PROG_TYPE_LWT_OUT
BPF_PROG_TYPE_LWT_XMIT
BPF_PROG_TYPE_LWT_SEG6LOCAL
BPF_PROG_TYPE_FLOW_DISSECTOR
BPF_PROG_TYPE_STRUCT_OPS
BPF_PROG_TYPE_RAW_TRACEPOINT
BPF_PROG_TYPE_SYSCALL
使用 BPF_PROG_RUN
命令时,用户空间提供一个输入上下文对象,以及(对于操作网络数据包的程序类型)包含 BPF 程序将操作的数据包数据的缓冲区。然后,内核将执行该程序并将结果返回到用户空间。请注意,程序在此模式下运行时不会产生任何副作用;特别是,数据包实际上不会被重定向或丢弃,程序返回代码只会返回到用户空间。下面单独记录了用于实时执行 XDP 程序的单独模式。
在“实时帧模式”下运行 XDP 程序¶
BPF_PROG_RUN
命令具有单独的模式来运行实时 XDP 程序,该模式可用于以一种方式执行 XDP 程序,在这种方式下,数据包将在 XDP 程序执行后由内核实际处理,就像它们到达物理接口一样。当向 BPF_PROG_RUN
提供 XDP 程序时,通过设置 BPF_F_TEST_XDP_LIVE_FRAMES
标志来激活此模式。
实时数据包模式针对多次高性能执行提供的 XDP 程序进行了优化(例如,适合作为流量生成器运行),这意味着语义不如常规测试运行模式那么直接。具体来说:
在实时帧模式下执行 XDP 程序时,执行结果不会返回到用户空间;相反,内核将执行程序返回代码指示的操作(丢弃数据包、重定向数据包等)。因此,在这种模式下运行时,设置系统调用参数中的
data_out
或ctx_out
属性将被拒绝。此外,并非所有失败都会直接报告回用户空间;具体来说,只有在设置或执行期间(如内存分配错误)发生致命错误才会停止执行并返回错误。如果在数据包处理中发生错误(如无法重定向到给定接口),则执行将继续下一次重复;这些错误可以通过与常规 XDP 程序相同的跟踪点来检测。用户空间可以将 ifindex 作为上下文对象的一部分提供,就像在常规(非实时)模式中一样。XDP 程序将执行,就好像数据包到达此接口一样;也就是说,上下文对象的
ingress_ifindex
将指向该接口。此外,如果 XDP 程序返回XDP_PASS
,则数据包将注入到内核网络堆栈中,就好像它到达该 ifindex 一样;如果它返回XDP_TX
,则数据包将从同一接口传出。但请注意,由于程序执行不是在驱动程序上下文中发生的,因此XDP_TX
实际上转换为与XDP_REDIRECT
到同一接口相同的操作(即,只有当驱动程序支持ndo_xdp_xmit
驱动程序 op 时才有效)。当多次重复运行程序时,执行将分批进行。批次大小默认为 64 个数据包(与最大 NAPI 接收批次大小相同),但用户空间可以通过
batch_size
参数指定,最多 256 个数据包。对于每个批次,内核重复执行 XDP 程序,每次调用都获得数据包数据的单独副本。对于每次重复,如果程序丢弃数据包,则数据页将立即回收(请参见下文)。否则,数据包将被缓冲,直到批次结束,此时,在此批次期间以此方式缓冲的所有数据包都将一次性传输。设置测试运行时,内核将初始化一个大小与批次大小相同的内存页池。每个内存页都将使用用户空间在
BPF_PROG_RUN
调用时提供的初始数据包数据进行初始化。在可能的情况下,页面将在未来的程序调用中回收,以提高性能。通常,页面将一次回收整个批次,除非数据包被丢弃(通过返回代码或由于,例如,重定向错误),在这种情况下,该页面将立即回收。如果数据包最终传递到常规网络堆栈(因为 XDP 程序返回XDP_PASS
,或者因为它最终被重定向到将其注入堆栈的接口),则该页面将被释放,并在池为空时分配一个新页面。回收时,页面内容不会重写;只有上下文对象中的数据包边界指针(
data
、data_end
和data_meta
)将重置为原始值。这意味着如果程序重写了数据包内容,则必须准备好在后续调用中看到原始内容或修改后的版本。