从用户空间运行BPF程序

本文档描述了用于从用户空间运行BPF程序的BPF_PROG_RUN设施。

概述

BPF_PROG_RUN命令可以通过bpf()系统调用在内核中执行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_outctx_out属性将被拒绝。此外,并非所有失败都会直接报告回用户空间;具体来说,只有设置或执行过程中的致命错误(如内存分配错误)才会中止执行并返回错误。如果数据包处理中发生错误,例如重定向到给定接口失败,执行将继续到下一个重复;这些错误可以通过与常规XDP程序相同的跟踪点检测到。

  • 用户空间可以像常规(非实时)模式一样,将一个ifindex作为上下文对象的一部分提供。XDP程序将如同数据包抵达该接口一样执行;也就是说,上下文对象的ingress_ifindex将指向该接口。此外,如果XDP程序返回XDP_PASS,数据包将被注入内核网络堆栈,如同其抵达该ifindex一样;如果返回XDP_TX,数据包将从同一接口传出。然而请注意,由于程序执行并非发生在驱动程序上下文中,一个XDP_TX实际上会转变为与一个XDP_REDIRECT到同一接口相同的操作(即,仅当驱动程序支持ndo_xdp_xmit驱动程序操作时才有效)。

  • 当程序进行多次重复运行时,执行将以批处理方式进行。批处理大小默认为64个数据包(与NAPI最大接收批处理大小相同),但用户空间可以通过batch_size参数指定,最大可达256个数据包。对于每个批处理,内核会重复执行XDP程序,每次调用都会获得数据包数据的单独副本。对于每次重复,如果程序丢弃数据包,数据页将立即回收(参见下文)。否则,数据包将一直缓冲到批处理结束,届时在此批处理期间以这种方式缓冲的所有数据包将一次性传输。

  • 设置测试运行时,内核将初始化一个大小与批处理大小相同的内存页池。每个内存页都将使用用户空间在调用BPF_PROG_RUN时提供的初始数据包数据进行初始化。如果可能,这些页将在未来的程序调用中被回收,以提高性能。页面通常会一次回收一个完整的批处理,但当数据包被丢弃时(通过返回码或例如重定向错误),该页面将立即回收。如果数据包最终被传递到常规网络堆栈(因为XDP程序返回XDP_PASS,或者因为它最终被重定向到将数据包注入堆栈的接口),则该页面将被释放,并在页面池为空时分配一个新的页面。

    回收时,页面内容不会被重写;上下文对象中只有数据包边界指针(datadata_enddata_meta)会重置为原始值。这意味着如果程序重写了数据包内容,则在后续调用中必须准备好看到原始内容或修改后的版本。