为了回到这个问题,我们在bpf.h中看到了一些bpf类型的枚举定义:
enum bpf_prog_type {
BPF_PROG_TYPE_UNSPEC,
BPF_PROG_TYPE_SOCKET_FILTER,
BPF_PROG_TYPE_KPROBE,
BPF_PROG_TYPE_SCHED_CLS,
BPF_PROG_TYPE_SCHED_ACT,
BPF_PROG_TYPE_TRACEPOINT,
BPF_PROG_TYPE_XDP,
BPF_PROG_TYPE_PERF_EVENT,
BPF_PROG_TYPE_CGROUP_SKB,
BPF_PROG_TYPE_CGROUP_SOCK,
BPF_PROG_TYPE_LWT_IN,
BPF_PROG_TYPE_LWT_OUT,
BPF_PROG_TYPE_LWT_XMIT,
BPF_PROG_TYPE_SOCK_OPS,
BPF_PROG_TYPE_SK_SKB,
};
为了解释这些问题,我们不妨问自己几个问题:
首先,我们使用socket相关的程序类型,可以对socket的data进行过滤,转发和监控。
当观察网络时,我们知希望看到一部分的网络流量,比如,来自故障系统的所有流量。Filters用于描述我们希望看到的流量,理想情况下,我们希望它是快速的,并且我们希望为用户提供一组开放式的过滤选项。但是有一个问题,我们希望尽早丢弃不需要的数据,并且需要在内核上下文中进行过滤。考虑在内核实现,因为在考虑将数据包复制到用户态的成本。发明了一种安全的迷你语言,可以将高级过滤器转换为内核可以使用的字节码程序(bpf),该语言的目的是在快速且安全的同时支持一组灵活的过滤选项。可以使用诸如tcpdump之类的用户空间程序推送用这种类似于汇编语言编写的过滤器,以完成内核中的过滤。
对于套接字过滤,通常的情况是将其附加到原始套接字(SOCK_RAW),实际上,您会注意到大多数执行套接字过滤的程序都具有以下行:
s = socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
创建这样的套接字,我们指定域(AF_PACKET),套接字类型(SOCK_RAW)和协议(所有数据包类型)。在Linux内核中,原始数据包的接收由raw_local_deliver()函数实现。 在调用相关IP协议的处理程序之前,ip_local_deliver_finish()调用了该方法,该处理程序是将数据包传递到TCP,UDP,ICMP等的地方。因此在这里,流量还没有和具体的socket关联起来。在IP层,当计算出来packet和4层(传输层)的map时,然后再和具体的socket关联起来。你可以看到cBPF的字节码通过tcpdump的-d选项。这里,我想在wlp4s0的interface上运行tcpdump。
# tcpdump -i wlp4s0 -d 'tcp'
(000) ldh [12]
(001) jeq #0x86dd jt 2 jf 7
(002) ldb [20]
(003) jeq #0x6 jt 10 jf 4
(004) jeq #0x2c jt 5 jf 11
(005) ldb [54]
(006) jeq #0x6 jt 10 jf 11
(007) jeq #0x800 jt 8 jf 11
(008) ldb [23]
(009) jeq #0x6 jt 10 jf 11
(010) ret #65535
(011) ret #0
你可能会考虑套接字与netfilter的不同之处。Netfilter用NF_HOOK()定义定义了自己的一组钩子,基于netfilter的技术(例如ipfilter)也可以用来过滤流量。你可能会想,我们可以在对应的地方用bpf,是的。在一些kernle中已经用bpfilter代替ipfilter。
接下来,我们再来看看与socket相关的filter
我可以用来干啥?
filter操作包括丢弃数据包(如果程序返回0)或修改数据包(如果程序返回的长度小于原始长度)。请参见sk_filter_trim_cap()及其对bpf_prog_run_save_cb()的调用。请注意,我们不是修改或者丢弃原始的数据包,这些数据包都会到达原始的套接字。我们处理的是原始数据包元数据的copy,这些copy的数据包可以被raw socket访问到。除此之外,我们还可以做一些其他的事情。例如进行流量统计在BPF 的Maps中。
如何attach我们的program?
它可以通过SO_ATTACH_BPF setsockopt()进行attach,
我可以获取哪些context?
struct __sk_buff 包含这个packet的metadata/data。他的结构在include/linux/ bpf.h中定义,并包含来自实际sk_buff的关键字段。 bpf验证程序将对有效的__sk_buff字段的访问转换为“真实”sk_buff的偏移量,有关更多详细信息,请参见https://lwn.net/Articles/636647/。
它什么时候被执行?
socket filter过滤器在sock_queue_rcv_skb()中运行以接收消息,该协议被各种协议(TCP,UDP,ICMP,原始套接字等)调用,可用于过滤inbound的流量。
在这里我们将创建一个过滤器,该过滤器会修改基于协议类型过滤的数据包数据。 对于IPv4 TCP,我们仅获取IPv4 + TCP标头,而对于UDP,仅获取IPv4和UDP标头。我们将不处理IPv4选项,因为这是一个简单的示例,因此在所有其他情况下,我们将返回0(丢弃数据包)。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bpf_helpers.h"
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
/*
* We are only interested in TCP/UDP headers, so drop every other protocol
* and trim packets after the TCP/UDP header by returning length of
* ether header + IPv4 header + TCP/UDP header.
*/
SEC("socket")
int bpf_prog1(struct __sk_buff *skb)
{
int proto = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol));
int size = ETH_HLEN + sizeof(struct iphdr);
switch (proto) {
case IPPROTO_TCP:
size += sizeof(struct tcphdr);
break;
case IPPROTO_UDP:
size += sizeof(struct udphdr);
break;
default:
size = 0;
break;
}
return size;
}
char _license[] SEC("license") = "GPL";
这个代码可以被LLVM/clang编译成bpf的bytecodes。它将包含一个带有“socket”的ELF section。下一步是使用BPF系统调用为程序分配文件描述符,然后将其附加到socket.在samples / bpf中,您可以看到bpf_load.c扫描了ELF节,并且名称以“ socket”为前缀的节被识别为BPF_PROG_TYPE_SOCKET_FILTER程序。 如果要添加示例,我建议包括bpf_load.h,这样您就可以在BPF程序上调用load_bpf_file()了。例如,在samples / bpf / sockex1_user.c中,我们获取程序的文件名(sockex1)并加载sockex1_kern.o; 相关的BPF程序。 然后,我们打开一个原始套接字进行环回(lo)并将程序附加到该位置:
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
if (load_bpf_file(filename)) {
printf("%s", bpf_log_buf);
return 1;
}
sock = open_raw_sock("lo");
assert(setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, prog_fd,
sizeof(prog_fd[0])) == 0);
能用它来干什么?
attach a BPF程序去获取socket的一些操作,比如连接的建立,重传超时等。一旦捕捉到相关选项,也可以通过bpf setsockopt()设置,因此,例如,在来自不同子网的系统的被动连接建立时,我们可以降低MTU,这样我们就不必担心中间路由器会分割数据包。程序可以返回成功(0)或失败(负值),并且可以设置reply value以指示套接字选项的所需值(例如TCP rwnd)。有关完整的详细信息,请参见https://lwn.net/Articles/727189/,并在include/net/ tcp.h中查找tcp_call_bpf()的内联定义,以了解TCP如何处理此类程序。另一个用例是结合BPF_PROG_TYPE_SK_SKB程序进行sockmap更新。 传递到BPF_PROG_TYPE_SOCK_OPS程序中的bpf_sock_ops结构指针用于更新sockmap,为该套接字关联一个值。 以后的sk_skb程序可以引用这些值,以指定通过bpf_sk_redirect_map()调用重定向到哪个套接字。 如果这听起来令人困惑,我建议您看一下sample / sockmap中的代码。
我如何attach我的程序?
使用BPF_CGROUP_SOCK_OPS附加类型将其附加到cgroup文件描述符。
获取到怎么样的上下文?
提供的参数是上下文struct bpf_sock_ops * … Op字段指定操作,BPF_SOCK_OPS_RWND_INIT,BPF_SOCK_OPS_TCP_CONNECT_CB等。The reply field can be used to indicate to the caller a new value for a parameter set.
/* User bpf_sock_ops struct to access socket values and specify request ops
* and their replies.
* Some of this fields are in network (bigendian) byte order and may need
* to be converted before use (bpf_ntohl() defined in samples/bpf/bpf_endian.h).
* New fields can only be added at the end of this structure
*/
struct bpf_sock_ops {
__u32 op;
union {
__u32 reply;
__u32 replylong[4];
};
__u32 family;
__u32 remote_ip4; /* Stored in network byte order */
__u32 local_ip4; /* Stored in network byte order */
__u32 remote_ip6[4]; /* Stored in network byte order */
__u32 local_ip6[4]; /* Stored in network byte order */
__u32 remote_port; /* Stored in network byte order */
__u32 local_port; /* stored in host byte order */
};
什么时候运行?
与期望在代码库中的特定位置调用其他BPF程序类型不同.SOCK_OPS程序可以在不同的地方被调用,然后使用op字段去表明context.请参见include / uapi / linux / bpf.h,但其中包括重传超时,连接建立等事件。
可以用来干啥?
允许用户访问skb和套接字详细信息,例如端口和IP地址,以支持套接字之间skb的重定向。 参见https://lwn.net/Articles/731133/,此功能与sockmap结合使用(sockmap是一种特殊的BPF映射,其中包含对套接字结构和相关值的引用)。sockmaps被用来支持转发,可以使用bpf_sk_redirect_map()helper 器执行重定向。通用的方法是我们使用sock_ops BPF程序来获取socket的创建事件,将这些值与sockmap关联起来,然后使用sk_skb检测点的数据来通知套接字重定向。这种程序类型的另一个用例是在strparser框架中–https://www.kernel.org/doc/Documentation/networking/strparser.txt
如何加载我的程序?
重定向程序作为BPF_SK_SKB_STREAM_VERDICT附加到sockmap; 它应返回bpf_sk_redirect_map()的结果。 一个strparser程序通过BPF_SK_SKB_STREAM_PARSER附加,并且应返回已解析数据的长度。
能够获取什么样的context?
指向包含包元数据/数据的结构__sk_buff的指针。 但是,sk_skb程序类型可以访问更多字段。 可用的额外字段集记录在include / linux / bpf.h中,如下所示:
/* Accessed by BPF_PROG_TYPE_sk_skb types from here to ... */
__u32 family;
__u32 remote_ip4; /* Stored in network byte order */
__u32 local_ip4; /* Stored in network byte order */
__u32 remote_ip6[4]; /* Stored in network byte order */
__u32 local_ip6[4]; /* Stored in network byte order */
__u32 remote_port; /* Stored in network byte order */
__u32 local_port; /* stored in host byte order */
/* ... here. */
因此,仅从上面我们就可以看到我们可以收集有关套接字的信息,因为上面的内容代表了唯一标识套接字的关键信息(协议在__sk_buff结构的全局可访问部分中已经可用)。
什么时候会运行?
可以通过把BPF_SK_SKB_STREAM_PARSER 附加到sockmap上来把一个stream parser附加到一个socket上,然后,当socket通过、bpf/sockmap.c中的smap_parse_func_strparser() 接受的时候,就会执行。BPF_SK_SKB_STREAM_VERDICT也会附加到sockmap上,它通过smap_verdict_func()来执行。
接下来,让我们检查与TC内核数据包调度子系统有关的程序类型。 可以通过tc(8)的manpage查看相关的具体信息。
有何用处?
tc_cls_act允许我们在Linux QoS子系统–tc中,去使用BPF程序进行相关的动作。tc(8)还支持eBpf,因此我们可以将BPF程序直接作为入站(入站)和出站(出站)流量的分类器和操作加载。有关如何使用tc的BPF功能的说明,请参见http://man7.org/linux/man-pages/man8/tc-bpf.8.html。 tc程序可以分类,修改,重定向或丢弃数据包。
如何attach 程序?
可以使用tc(8); 有关详细信息,请参见tc-bpf(8)。 基础是我们为网络设备创建一个“ clsact” qdisc,并通过指定BPF对象和相关的ELF部分来添加入口分类器/出口分类器/ actoins。 例如,要从myprog_kernel.o(bpf-bytecode编译的目标文件)的ELF节my_elf_sec中的eth0添加入口分类器:
tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj myprog_kernel.o sec my_elf_sec
提供的上下文?
指向结构__sk_buff数据包元数据/数据的指针。
什么时候运行?
如上所述,必须添加分类器qdisc,一旦添加分类器qdisc,我们就可以附加BPF程序对入站和出站流量进行分类。 在实现方面,act_bpf.c和cls_bpf.c实现操作/分类器模块。 在入口/入口sch_handle_ingress()/ egress()上,调用tcf_classify()。 在进入的情况下,我们通过核心网络接口的接收功能进行分类,因此我们在驱动程序处理完数据包之后但在IP等看到数据包之前就得到了数据包。 在出口上,过滤是在提交给设备队列进行传输之前完成的。
XDP的主要设计目标是在网络数据路径中引入可编程性。 目的是提供尽可能靠近设备的XDP挂钩(在OS创建sk_buff元数据之前),以便在支持跨设备的通用基础结构的同时最大化性能。 要支持这种XDP,需要更改驱动程序。 有关示例,请参阅drivers / net / ethernet / broadcom / bnxt / bnxt_xdp.c。 添加了一个bpf网络设备操作(ndo_bpf)。 对于bnxt,它支持XDP_SETUP_PROG和XDP_QUERY_PROG操作; 前者将设备配置为XDP,保留rings并将程序设置为活动状态。 后者返回BPF程序ID。 BPF特定的发送和接收功能由实际的发送/接收功能提供并在需要时调用。
可以用来干啥?
XDP允许在分配了分组元数据(结构sk_buff)之前尽早访问分组数据。 因此,这是进行DDoS缓解或负载平衡的有用位置,因为此类活动通常可以避免sk_buff分配的昂贵开销。 XDP旨在通过BPF挂钩来支持内核的运行时编程,但是要与内核本身协同工作。 即不是内核旁路机制。 支持的动作包括XDP_PASS(照常进入网络处理),XDP_DROP(丢弃),XDP_TX(发送)和XDP_REDIRECT。 请参阅include / uapi / linux / bpf.h以获取“枚举xdp_action”。
如何attach 程序?
通过netlink套接字消息。 创建并绑定了一个netlink套接字-socket(AF_NETLINK,SOCK_RAW,NETLINK_ROUTE),然后我们发送NLA_F_NESTED类型的netlink消息| 43; 这指定了XDP消息。 该消息包含BPF fd,接口索引(ifindex)。 有关示例,请参见samples / bpf / bpf_load.c。
context?
xdp元数据指针; struct xdp_md *。 XDP元数据是轻量级的; 来自include / uapi / linux / bpf.h:
/* user accessible metadata for XDP packet hook
* new fields must be added to the end of this structure
*/
struct xdp_md {
__u32 data;
__u32 data_end;
};
什么时候会执行?
真实” XDP在驱动程序级别实现,并且为XDP使用预留了发送/接收ring resources。 对于驱动程序不支持XDP的情况,可以选择使用“通用” XDP,它在net / core / dev.c中实现。 缺点是我们不绕过skb分配,它只允许我们将XDP用于此类设备。
kprobes,跟踪点和perf事件均提供内核检测。 kprobes-https://www.kernel.org/doc/Documentation/kprobes.txt-允许检测特定功能-可以通过kprobe监视功能的进入,以及功能中的大多数指令,或者可以进入/返回 通过kretprobe进行检测。 启用这些探针之一后,将保存启用点处的代码,并用断点指令替换。 当遇到此断点时,将生成陷阱指令,保存寄存器,然后跳转到相关的检测处理程序。 例如,kprobes由kprobe_dispatcher()处理,该函数获取kprobe的地址并将上下文注册为参数。 kretprobes是通过kprobes实现的; kprobe在进入时触发并修改返回地址,保存原始地址并将其替换为检测处理程序的位置。 跟踪点-https://www.kernel.org/doc/Documentation/trace/tracepoints.rst-类似,但是除了在特定说明中启用外,还可以在代码中的站点上进行显式标记,如果启用,则可以 在感兴趣的站点上收集调试信息。 可以在多个地方声明相同的跟踪点; 例如,在net / mac80211 / driver-ops.c中的多个位置调用trace_drv_return_int()。
Perf事件-https://perf.wiki.kernel.org/index.php/Main_Page-是ebpf支持这些程序类型的基础。ebpf允许我们可以将程序附加到感兴趣的perf事件上。这些事件包括kprobes,uprobes,tracepoint等以及其他软件事件,但是其实也可以监听一些硬件的事件。
这些检测点使BPF能够成为通用的跟踪工具,并成为支持原始的以网络为中心的用例(如套接字过滤)的手段。
可以干什么?
通过kprobe/kerprobe,注入到一些内核函数上。 k [ret] probe_perf_func()执行附加到探测点的BPF程序。请参见https://www.kernel.org/doc/Documentation/trace/uprobetracer.txt
如何attach?
当通过sysfs创建kprobe时,它具有与之关联的ID,存储在/ sys / kernel / debug / tracing / events / [uk] probe // id中,/ sys / kernel / debug / tracing / events / [uk ] retprobe / probename / id。 https://www.kernel.org/doc/Documentation/trace/kprobetrace.txt包含有关如何使用sysfs创建kprobe的详细信息。例如,在tcp_retransmit_skb()的条目上创建一个名为“ myprobe”的探针并检索其ID:
# echo 'p:myprobe tcp_retransmit_skb' > /sys/kernel/debug/tracing/kprobe_events
# cat /sys/kernel/debug/tracing/events/kprobes/myprobe/id
2266
我们可以使用该probe id打开一个perf事件,将其启用,并将该perf事件的BPF程序设置为我们的程序。 请参阅load_and_attach()函数中的samples / bpf / bpf_load.c,了解如何针对k [ret]探针执行此操作。 该代码可能看起来像这样:
struct perf_event_attr attr;
int eventfd, programfd;
int probeid;
/* Load BPF program and assign programfd to it; and get probeid of probe from sysfs */
attr.type = PERF_TYPE_TRACEPOINT;
attr.sample_type = PERF_SAMPLE_RAW;
attr.sample_period = 1;
attr.wakeup_events = 1;
attr.config = probeid;
eventfd = sys_perf_event_open(&attr, -1, 0, programfd, 0);
if (eventfd < 0)
return -errno;
if (ioctl(eventfd, PERF_EVENT_IOC_ENABLE, 0)) {
close(eventfd);
return -errno;
}
if (ioctl(eventfd, PERF_EVENT_IOC_SET_BPF, programfd)) {
close(eventfd);
return -errno;
}
context provided?
struct pt_regs * ctx,可以从中访问寄存器。 其中大部分是特定于平台的,但是存在一些通用函数,例如regs_return_value(regs),该函数返回寄存器的值,而不是保存函数返回值(x86上的regs→ax)。
什么时候执行?
当启用探针并命中断点时,k [ret] probe_perf_func()执行通过trace_call_bpf()附加到探针点的BPF程序。 u [ret] probe_perf_func()与此类似。
可以干啥?
内核代码中的仪器跟踪点。 可以通过sysfs启用跟踪点,这与kprobes一样,并且以类似的方式。 跟踪事件的列表可以在/ sys / kernel / debug / tracing / events下看到。
如何附加?
正如我们在上面看到的,当通过sysfs创建跟踪点时,它具有一个与之关联的ID。 我们可以使用该探针ID打开一个perf事件,将其启用,并将该perf事件的BPF程序设置为我们的程序。 有关如何对跟踪点执行此操作,请参见load_and_attach()函数中的samples / bpf / bpf_load.c。 上面针对kprobes的代码片段也适用于跟踪点。 作为显示如何启用跟踪点的示例,在这里,我们将net / net_dev_xmit跟踪点启用为“ myprobe2”并获取其ID:
# echo 'p:myprobe2 trace:net/net_dev_xmit' > /sys/kernel/debug/tracing/kprobe_events
# cat /sys/kernel/debug/tracing/events/kprobes/myprobe2/id
2270
context provid?
特定跟踪点提供的上下文; 参数和数据类型与跟踪点定义相关联。
什么时候运行?
启用并命中跟踪点后,perf_trace _()(请参见include / trace / perf.h中的定义)将调用perf_trace_run_bpf_submit(),后者将通过trace_call_bpf()调用bpf程序。
可以干啥?
得到软件和硬件性能事件。 这些事件包括系统调用,计时器到期,硬件事件采样等。硬件事件包括PMU事件(处理器监视单元),它告诉我们诸如已完成多少指令等信息。Perf事件监视可以针对特定的进程或组, 处理器,并且可以指定采样周期进行分析。
如何attach?
与上面的类似,我们使用perf_event_open()的一个属性类设置。通过PERF_EVENT_IOC_ENABLE ioctl()启用perf事件,并通过PERF_EVENT_IOC_SET_BPF ioctl()设置bpf程序。 对于PMU(处理器监视单元)perf事件示例,请参阅samples / bpf / sampleip_user.c中的以下片段:
struct perf_event_attr pe_sample_attr = {
.type = PERF_TYPE_SOFTWARE,
.freq = 1,
.sample_period = freq,
.config = PERF_COUNT_SW_CPU_CLOCK,
.inherit = 1,
};
...
pmu_fd[i] = sys_perf_event_open(&pe_sample_attr, -1 /* pid */, i,
-1 /* group_fd */, 0 /* flags */);
if (pmu_fd[i] < 0) {
fprintf(stderr, "ERROR: Initializing perf sampling\n");
return 1;
}
assert(ioctl(pmu_fd[i], PERF_EVENT_IOC_SET_BPF,
prog_fd[0]) == 0);
assert(ioctl(pmu_fd[i], PERF_EVENT_IOC_ENABLE, 0) == 0);
...
提供什么样的context?
struct bpf_perf_event_data {
struct pt_regs regs;
__u64 sample_period;
};
什么时候运行?
取决于perf事件触发和所选择的采样率,这些采样率由perf事件属性结构中的freq和sample_period字段指定。
CGroup用于处理资源分配,允许或拒绝进程组对系统资源(如CPU,网络带宽等)的访问而其活动却被各种名称空间(网络名称空间,进程ID名称空间等)隔离。 在BPF上下文中,我们可以创建允许或拒绝访问的eBPF程序。 在include / linux / bpf-cgroup.h中,我们可以看到用于执行socket / skb程序的定义,其中__cgroup_bpf_run_filter_skb被包装,以检查是否启用了cgroup BPF:
#define BPF_CGROUP_RUN_PROG_INET_INGRESS(sk, skb) \
({ \
int __ret = 0; \
if (cgroup_bpf_enabled) \
__ret = __cgroup_bpf_run_filter_skb(sk, skb, \
BPF_CGROUP_INET_INGRESS); \
\
__ret; \
})
#define BPF_CGROUP_RUN_SK_PROG(sk, type) \
({ \
int __ret = 0; \
if (cgroup_bpf_enabled) { \
__ret = __cgroup_bpf_run_filter_sk(sk, type); \
} \
__ret; \
})
如果启用了cgroup,我们会将程序附加到cgroup,它将在相关的挂钩点执行。 要了解钩子的完整列表,请查阅include / uapi / linux / bpf.h并检查BPF_CGROUP_ *定义的枚举类型“ bpf_attach_type”。
1.可以做什么?允许或拒绝IP出口/入口(BPF_CGROUP_INET_INGRESS / BPF_CGROUP_INET_EGRESS)上的网络访问。 BPF程序应返回1以允许访问。 任何其他值都会导致函数__cgroup_bpf_run_filter_skb()返回-EPERM,该值将传播到调用方,从而丢弃该数据包。
2.attach 程序?该程序将附加到特定cgroup的文件描述符。
3.什么时候执行?对于inet入口,sk_filter_trim_cap()(请参见上文)包含对BPF_CGROUP_RUN_PROG_INET_INGRESS(sk,skb)的调用; 如果返回非零值,则错误会传播给调用方(例如__sk_receive_skb()),并且数据包将被丢弃并释放。 在出口上也采用类似的方法,但是在ip[6]_finish_output()中。
1.可以干啥?在各种与套接字相关的事件(BPF_CGROUP_INET_SOCK_CREATE,BPF_CGROUP_SOCK_OPS)上允许或拒绝网络访问。 如上所述,BPF程序应返回1以允许访问。 任何其他值都会导致函数__cgroup_bpf_run_filter_sk()返回-EPERM,该值将传播到调用方,从而丢弃该数据包。
2.如何附加程序?
该程序将附加到特定cgroup的文件描述符。提供了什么上下文? 相关的socket(sk)。
3.什么时候运行?
在套接字创建时,在inet_create()中,我们以套接字为参数调用BPF_CGROUP_RUN_PROG_INET_SOCK(),如果该函数失败,则释放套接字。